@mediaway/ffi 0.1.6 → 0.2.1

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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Replay ring + MP4 payload placements (adr/container/0009-replay-ring-c-abi.md).
3
+ *
4
+ * Split out of index.ts, which sits under the workspace's 1000-line source cap. This module
5
+ * imports only leaf modules (koffi, loader), never index.ts, so there is no import cycle:
6
+ * index.ts calls {@link createReplayBindings} once, after it has defined `MwRational` and
7
+ * `MwPacketView`, whose koffi names the signatures below refer to.
8
+ */
9
+ import { type TypeObject } from "koffi";
10
+ /** `mediaway_replay_payload_kind_t`. */
11
+ export declare const REPLAY_PAYLOAD_BYTES = 0;
12
+ export declare const REPLAY_PAYLOAD_STORED = 1;
13
+ /** `mediaway_replay_ring_config_t`. */
14
+ export interface RawReplayRingConfig {
15
+ anchor_stream_id: number;
16
+ anchor_time_base: {
17
+ num: bigint | number;
18
+ den: number;
19
+ };
20
+ window_ms: bigint | number;
21
+ max_bytes: bigint | number;
22
+ payload_kind: number;
23
+ }
24
+ /** `mediaway_packet_meta_t`. */
25
+ export interface RawPacketMeta {
26
+ stream_id: number;
27
+ pts: bigint | number;
28
+ dts: bigint | number;
29
+ duration: bigint | number;
30
+ is_keyframe: boolean;
31
+ is_discard: boolean;
32
+ }
33
+ /** `mediaway_stored_payload_t`. */
34
+ export interface RawStoredPayload {
35
+ file: number;
36
+ offset: bigint | number;
37
+ len: number;
38
+ }
39
+ /** `mediaway_replay_clip_entry_t`. `payload` is BORROWED from the clip. */
40
+ export interface RawReplayClipEntry {
41
+ stream_id: number;
42
+ pts: bigint;
43
+ dts: bigint;
44
+ duration: bigint;
45
+ is_keyframe: boolean;
46
+ is_discard: boolean;
47
+ payload_kind: number;
48
+ payload: unknown;
49
+ payload_len: number;
50
+ stored_file: number;
51
+ stored_offset: bigint;
52
+ stored_len: number;
53
+ }
54
+ /** `mediaway_placement_t`. */
55
+ export interface RawPlacement {
56
+ track_id: number;
57
+ dts: bigint;
58
+ offset: bigint;
59
+ len: number;
60
+ }
61
+ /**
62
+ * Declare the replay structs and bind the functions.
63
+ *
64
+ * `rational` is index.ts's `MwRational` (the struct is registered there once; koffi rejects a
65
+ * second definition under the same name).
66
+ */
67
+ export declare function createReplayBindings(rational: TypeObject): {
68
+ structs: {
69
+ MwReplayRingConfig: TypeObject;
70
+ MwPacketMeta: TypeObject;
71
+ MwStoredPayload: TypeObject;
72
+ MwReplayClipEntry: TypeObject;
73
+ MwPlacement: TypeObject;
74
+ };
75
+ ringCreate: import("koffi").Callable;
76
+ ringAddStream: import("koffi").Callable;
77
+ ringPush: import("koffi").Callable;
78
+ ringPushStored: import("koffi").Callable;
79
+ ringSpanMs: import("koffi").Callable;
80
+ ringClipLast: import("koffi").Callable;
81
+ ringClose: import("koffi").Callable;
82
+ clipPacketCount: import("koffi").Callable;
83
+ clipDurationMs: import("koffi").Callable;
84
+ clipPacketAt: import("koffi").Callable;
85
+ clipFree: import("koffi").Callable;
86
+ muxerCreateWithPlacements: import("koffi").Callable;
87
+ muxerPollPlacements: import("koffi").Callable;
88
+ placementsFree: import("koffi").Callable;
89
+ };
90
+ /**
91
+ * `sizeof` and member offsets of the replay structs as koffi lays them out, for the test that
92
+ * pins them against a gcc probe of the real header.
93
+ */
94
+ export declare function replayLayout(structs: ReturnType<typeof createReplayBindings>["structs"]): {
95
+ replayRingConfig: {
96
+ size: number;
97
+ offsets: {
98
+ [k: string]: number;
99
+ };
100
+ };
101
+ packetMeta: {
102
+ size: number;
103
+ offsets: {
104
+ [k: string]: number;
105
+ };
106
+ };
107
+ storedPayload: {
108
+ size: number;
109
+ offsets: {
110
+ [k: string]: number;
111
+ };
112
+ };
113
+ replayClipEntry: {
114
+ size: number;
115
+ offsets: {
116
+ [k: string]: number;
117
+ };
118
+ };
119
+ placement: {
120
+ size: number;
121
+ offsets: {
122
+ [k: string]: number;
123
+ };
124
+ };
125
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Replay ring + MP4 payload placements (adr/container/0009-replay-ring-c-abi.md).
3
+ *
4
+ * Split out of index.ts, which sits under the workspace's 1000-line source cap. This module
5
+ * imports only leaf modules (koffi, loader), never index.ts, so there is no import cycle:
6
+ * index.ts calls {@link createReplayBindings} once, after it has defined `MwRational` and
7
+ * `MwPacketView`, whose koffi names the signatures below refer to.
8
+ */
9
+ import koffi, {} from "koffi";
10
+ import { containerLib } from "./loader.js";
11
+ /** `mediaway_replay_payload_kind_t`. */
12
+ export const REPLAY_PAYLOAD_BYTES = 0;
13
+ export const REPLAY_PAYLOAD_STORED = 1;
14
+ /**
15
+ * Declare the replay structs and bind the functions.
16
+ *
17
+ * `rational` is index.ts's `MwRational` (the struct is registered there once; koffi rejects a
18
+ * second definition under the same name).
19
+ */
20
+ export function createReplayBindings(rational) {
21
+ const MwReplayRingConfig = koffi.struct("MwReplayRingConfig", {
22
+ anchor_stream_id: "uint32",
23
+ anchor_time_base: rational,
24
+ window_ms: "uint64",
25
+ max_bytes: "uint64",
26
+ payload_kind: "int32",
27
+ });
28
+ const MwPacketMeta = koffi.struct("MwPacketMeta", {
29
+ stream_id: "uint32",
30
+ pts: "int64",
31
+ dts: "int64",
32
+ duration: "uint64",
33
+ is_keyframe: "bool",
34
+ is_discard: "bool",
35
+ });
36
+ const MwStoredPayload = koffi.struct("MwStoredPayload", {
37
+ file: "uint32",
38
+ offset: "uint64",
39
+ len: "uint32",
40
+ });
41
+ const MwReplayClipEntry = koffi.struct("MwReplayClipEntry", {
42
+ stream_id: "uint32",
43
+ pts: "int64",
44
+ dts: "int64",
45
+ duration: "uint64",
46
+ is_keyframe: "bool",
47
+ is_discard: "bool",
48
+ payload_kind: "int32",
49
+ payload: "uint8_t *",
50
+ payload_len: "size_t",
51
+ stored_file: "uint32",
52
+ stored_offset: "uint64",
53
+ stored_len: "uint32",
54
+ });
55
+ const MwPlacement = koffi.struct("MwPlacement", {
56
+ track_id: "uint32",
57
+ dts: "int64",
58
+ offset: "uint64",
59
+ len: "uint32",
60
+ });
61
+ return {
62
+ structs: { MwReplayRingConfig, MwPacketMeta, MwStoredPayload, MwReplayClipEntry, MwPlacement },
63
+ ringCreate: containerLib.func("int mediaway_replay_ring_create(MwReplayRingConfig *config, _Out_ void **out_ring)"),
64
+ ringAddStream: containerLib.func("int mediaway_replay_ring_add_stream(void *ring, uint32_t stream_id, MwRational time_base)"),
65
+ ringPush: containerLib.func("int mediaway_replay_ring_push(void *ring, MwPacketView *packet)"),
66
+ ringPushStored: containerLib.func("int mediaway_replay_ring_push_stored(void *ring, MwPacketMeta *meta, MwStoredPayload *stored)"),
67
+ ringSpanMs: containerLib.func("int mediaway_replay_ring_span_ms(void *ring, _Out_ uint64_t *out_span_ms)"),
68
+ ringClipLast: containerLib.func("int mediaway_replay_ring_clip_last(void *ring, uint64_t span_ms, _Out_ void **out_clip, _Out_ bool *out_has)"),
69
+ ringClose: containerLib.func("void mediaway_replay_ring_close(void *ring)"),
70
+ clipPacketCount: containerLib.func("size_t mediaway_replay_clip_packet_count(void *clip)"),
71
+ clipDurationMs: containerLib.func("int mediaway_replay_clip_duration_ms(void *clip, _Out_ uint64_t *out_ms)"),
72
+ clipPacketAt: containerLib.func("int mediaway_replay_clip_packet_at(void *clip, size_t index, _Out_ MwReplayClipEntry *out_entry)"),
73
+ clipFree: containerLib.func("void mediaway_replay_clip_free(void *clip)"),
74
+ muxerCreateWithPlacements: containerLib.func("void *mediaway_muxer_create_with_placements()"),
75
+ muxerPollPlacements: containerLib.func("int mediaway_muxer_poll_placements(void *muxer, _Out_ MwPlacement **out_rows, _Out_ size_t *out_count)"),
76
+ placementsFree: containerLib.func("void mediaway_placements_free(MwPlacement *rows, size_t count)"),
77
+ };
78
+ }
79
+ /**
80
+ * `sizeof` and member offsets of the replay structs as koffi lays them out, for the test that
81
+ * pins them against a gcc probe of the real header.
82
+ */
83
+ export function replayLayout(structs) {
84
+ const describe = (type, members) => ({
85
+ size: koffi.sizeof(type),
86
+ offsets: Object.fromEntries(members.map((m) => [m, koffi.offsetof(type, m)])),
87
+ });
88
+ return {
89
+ replayRingConfig: describe(structs.MwReplayRingConfig, [
90
+ "anchor_stream_id", "anchor_time_base", "window_ms", "max_bytes", "payload_kind",
91
+ ]),
92
+ packetMeta: describe(structs.MwPacketMeta, [
93
+ "stream_id", "pts", "dts", "duration", "is_keyframe", "is_discard",
94
+ ]),
95
+ storedPayload: describe(structs.MwStoredPayload, ["file", "offset", "len"]),
96
+ replayClipEntry: describe(structs.MwReplayClipEntry, [
97
+ "stream_id", "pts", "dts", "duration", "is_keyframe", "is_discard", "payload_kind",
98
+ "payload", "payload_len", "stored_file", "stored_offset", "stored_len",
99
+ ]),
100
+ placement: describe(structs.MwPlacement, ["track_id", "dts", "offset", "len"]),
101
+ };
102
+ }
package/dist/index.d.ts CHANGED
@@ -14,11 +14,12 @@
14
14
  * `_free` — the public packages do this automatically.
15
15
  */
16
16
  import { type TypeObject } from "koffi";
17
- export declare function findLibrary(name: string): string;
18
- export declare const containerLib: import("koffi").LibraryHandle;
19
- export declare const pipelineLib: import("koffi").LibraryHandle;
20
- export declare const deviceLib: import("koffi").LibraryHandle;
21
- export declare const MwRational: TypeObject;
17
+ import { findLibrary, containerLib, pipelineLib, deviceLib } from "./loader.js";
18
+ import { MwRational } from "./rational.js";
19
+ import { replayLayout } from "./container-replay.js";
20
+ export * from "./container-replay.js";
21
+ export { findLibrary, containerLib, pipelineLib, deviceLib, MwRational };
22
+ export * from "./pipeline-v7.js";
22
23
  export declare const MwVideoTrackInfo: TypeObject;
23
24
  export declare const MwAudioTrackInfo: TypeObject;
24
25
  export declare const MwPacketView: TypeObject;
@@ -47,7 +48,6 @@ export declare const MwAudioStreamInfo: TypeObject;
47
48
  export declare const MwDecodePacketView: TypeObject;
48
49
  export declare const MwAutoVideoDecodeConfig: TypeObject;
49
50
  export declare const MwDecodedVideoFrame: TypeObject;
50
- export declare const MwAudioDecodeConfig: TypeObject;
51
51
  export declare const MwDecodedAudioFrame: TypeObject;
52
52
  export declare const container: {
53
53
  abiVersion: import("koffi").Callable;
@@ -136,6 +136,13 @@ export declare const container: {
136
136
  wavParse: import("koffi").Callable;
137
137
  };
138
138
  export declare const pipeline: {
139
+ sessionWriteFrameFromCameraCapture: import("koffi").Callable;
140
+ sessionWriteFrameFromDesktopCapture: import("koffi").Callable;
141
+ audioDecodeConfigAac: import("koffi").Callable;
142
+ sessionPollBytes: import("koffi").Callable;
143
+ encoderSupportAt: import("koffi").Callable;
144
+ encoderSupportFree: import("koffi").Callable;
145
+ decoderSupport: import("koffi").Callable;
139
146
  abiVersion: import("koffi").Callable;
140
147
  encConfigNew: import("koffi").Callable;
141
148
  encConfigH264: import("koffi").Callable;
@@ -169,8 +176,6 @@ export declare const pipeline: {
169
176
  audioDecodeSessionFlush: import("koffi").Callable;
170
177
  audioDecodeSessionClose: import("koffi").Callable;
171
178
  decodedAudioFrameFree: import("koffi").Callable;
172
- sessionWriteFrameFromCameraCapture: import("koffi").Callable;
173
- sessionWriteFrameFromDesktopCapture: import("koffi").Callable;
174
179
  };
175
180
  export declare const device: {
176
181
  abiVersion: import("koffi").Callable;
@@ -183,6 +188,8 @@ export declare const device: {
183
188
  cameraClose: import("koffi").Callable;
184
189
  cameraFrameFree: import("koffi").Callable;
185
190
  desktopConfigScreen: import("koffi").Callable;
191
+ desktopConfigWindow: import("koffi").Callable;
192
+ desktopBorderHidden: import("koffi").Callable;
186
193
  desktopOpen: import("koffi").Callable;
187
194
  desktopGeometry: import("koffi").Callable;
188
195
  desktopPollFrame: import("koffi").Callable;
@@ -201,6 +208,31 @@ export declare const device: {
201
208
  gpuDeviceHandle: import("koffi").Callable;
202
209
  gpuDeviceClose: import("koffi").Callable;
203
210
  };
211
+ export declare const replay: {
212
+ structs: {
213
+ MwReplayRingConfig: TypeObject;
214
+ MwPacketMeta: TypeObject;
215
+ MwStoredPayload: TypeObject;
216
+ MwReplayClipEntry: TypeObject;
217
+ MwPlacement: TypeObject;
218
+ };
219
+ ringCreate: import("koffi").Callable;
220
+ ringAddStream: import("koffi").Callable;
221
+ ringPush: import("koffi").Callable;
222
+ ringPushStored: import("koffi").Callable;
223
+ ringSpanMs: import("koffi").Callable;
224
+ ringClipLast: import("koffi").Callable;
225
+ ringClose: import("koffi").Callable;
226
+ clipPacketCount: import("koffi").Callable;
227
+ clipDurationMs: import("koffi").Callable;
228
+ clipPacketAt: import("koffi").Callable;
229
+ clipFree: import("koffi").Callable;
230
+ muxerCreateWithPlacements: import("koffi").Callable;
231
+ muxerPollPlacements: import("koffi").Callable;
232
+ placementsFree: import("koffi").Callable;
233
+ };
234
+ /** koffi's sizeof/offsetof for the replay structs — pinned by test/replay-ring.test.ts. */
235
+ export declare const replayStructLayout: () => ReturnType<typeof replayLayout>;
204
236
  /** Copy `len` bytes out of a koffi pointer/opaque handle. */
205
237
  export declare function copyBytes(ptr: unknown, len: number): Buffer;
206
238
  /**
@@ -337,6 +369,21 @@ export interface RawPipelineFrame {
337
369
  }
338
370
  /** `mediaway_gpu_device_handle_t` (Screen capture / GPU-input encode input,
339
371
  * mediaway_gpu_device_handle()'s output). */
372
+ export interface RawDesktopConfig {
373
+ source_kind: number;
374
+ source_index: number;
375
+ time_base: RawRational;
376
+ gpu_device: RawGpuDeviceHandle;
377
+ window_handle: bigint | number;
378
+ cursor: number;
379
+ border: number;
380
+ dimensions: number;
381
+ region_x: number;
382
+ region_y: number;
383
+ region_width: number;
384
+ region_height: number;
385
+ region_enabled: boolean;
386
+ }
340
387
  export interface RawGpuDeviceHandle {
341
388
  kind: number;
342
389
  native: number | bigint;
@@ -417,12 +464,14 @@ export interface RawDecodedVideoFrame {
417
464
  data: unknown;
418
465
  data_len: number;
419
466
  }
420
- /** `mediaway_audio_decode_config_t` (Opus only today). */
467
+ /** `mediaway_audio_decode_config_t` (Opus or AAC; AAC needs `extra_data`, the ASC). */
421
468
  export interface RawAudioDecodeConfig {
422
469
  codec: number;
423
470
  sample_rate: number;
424
471
  channels: number;
425
472
  time_base: RawRational;
473
+ extra_data: unknown;
474
+ extra_data_len: number;
426
475
  }
427
476
  /** `mediaway_decoded_audio_frame_t` (audio decode session output; always F32). */
428
477
  export interface RawDecodedAudioFrame {
package/dist/index.js CHANGED
@@ -14,63 +14,17 @@
14
14
  * `_free` — the public packages do this automatically.
15
15
  */
16
16
  import koffi, {} from "koffi";
17
- import fs from "node:fs";
18
- import path from "node:path";
19
- import { fileURLToPath } from "node:url";
20
- // ── Library discovery ──────────────────────────────────────────────────────────
21
- // The cdylibs are Rust build artifacts, not installed system libraries. Search:
22
- // 1. $MEDIAWAY_FFI_DIR
23
- // 2. <this package>/native (DLLs bundled at pack time — the npm distribution)
24
- // 3. <repo root>/target/x86_64-pc-windows-gnu/debug (GNU toolchain, dev runs)
25
- // 4. <repo root>/target/debug (host/MSVC toolchain)
26
- // 5. cwd
27
- const here = path.dirname(fileURLToPath(import.meta.url));
28
- const repoRoot = path.resolve(here, "..", "..", "..", "..", "..");
29
- const searchDirs = [
30
- process.env.MEDIAWAY_FFI_DIR ?? "",
31
- path.resolve(here, "..", "native"),
32
- path.join(repoRoot, "target", "x86_64-pc-windows-gnu", "debug"),
33
- path.join(repoRoot, "target", "debug"),
34
- process.cwd(),
35
- ];
36
- export function findLibrary(name) {
37
- for (const dir of searchDirs) {
38
- if (!dir)
39
- continue;
40
- const candidate = path.join(dir, name);
41
- if (fs.existsSync(candidate))
42
- return candidate;
43
- }
44
- throw new Error(`cannot find ${name}; set $MEDIAWAY_FFI_DIR or build the -ffi crates`);
45
- }
46
- /**
47
- * cdylib filename Cargo produces for this platform. Windows and Linux only —
48
- * the workspace's own hardware/CI coverage is limited to those two (see
49
- * docs/ai/wiki/platform/order.md); macOS support is not claimed here since
50
- * it has never been built or run.
51
- */
52
- function libraryFilename() {
53
- switch (process.platform) {
54
- case "win32":
55
- return "mediaway_ffi.dll";
56
- case "linux":
57
- return "libmediaway_ffi.so";
58
- default:
59
- throw new Error(`mediaway: unsupported platform ${process.platform} (Windows and Linux only)`);
60
- }
61
- }
62
- function load(name) {
63
- return koffi.load(findLibrary(name));
64
- }
65
- const libraryName = libraryFilename();
66
- export const containerLib = load(libraryName);
67
- export const pipelineLib = load(libraryName);
68
- export const deviceLib = load(libraryName);
17
+ import { findLibrary, containerLib, pipelineLib, deviceLib } from "./loader.js";
18
+ import { MwRational } from "./rational.js";
19
+ // Pipeline ABI v7 (streaming bytes, AAC decode, capability probes) lives in its own module for
20
+ // the 1000-line source cap. Importing it first also registers `MwAudioDecodeConfig`, which the
21
+ // `pipeline` object's prototype strings below name.
22
+ import { pipelineV7 } from "./pipeline-v7.js";
23
+ import { createReplayBindings, replayLayout } from "./container-replay.js";
24
+ export * from "./container-replay.js";
25
+ export { findLibrary, containerLib, pipelineLib, deviceLib, MwRational };
26
+ export * from "./pipeline-v7.js";
69
27
  // ── Structs (layouts mirror the headers exactly) ───────────────────────────────
70
- export const MwRational = koffi.struct("MwRational", {
71
- num: "uint64",
72
- den: "uint32",
73
- });
74
28
  export const MwVideoTrackInfo = koffi.struct("MwVideoTrackInfo", {
75
29
  id: "uint32",
76
30
  codec: "int32",
@@ -182,11 +136,22 @@ export const MwCameraFrame = koffi.struct("MwCameraFrame", {
182
136
  data: "uint8_t *",
183
137
  data_len: "size_t",
184
138
  });
139
+ // Field order and widths mirror `mediaway_desktop_capture_config_t` in device.h
140
+ // exactly (ADR-0005). Every field after `gpu_device` is zero-means-previous-behaviour.
185
141
  export const MwDesktopConfig = koffi.struct("MwDesktopConfig", {
186
142
  source_kind: "int32",
187
143
  source_index: "uint32",
188
144
  time_base: MwRational,
189
145
  gpu_device: MwGpuDeviceHandle,
146
+ window_handle: "uint64",
147
+ cursor: "int32", // 0 = excluded, 1 = included
148
+ border: "int32", // 0 = shown, 1 = hidden (Window only)
149
+ dimensions: "int32", // 0 = native, 1 = even-cropped (Window only)
150
+ region_x: "uint32",
151
+ region_y: "uint32",
152
+ region_width: "uint32",
153
+ region_height: "uint32",
154
+ region_enabled: "bool",
190
155
  });
191
156
  export const MwDesktopFrame = koffi.struct("MwDesktopFrame", {
192
157
  pts: "int64",
@@ -295,12 +260,7 @@ export const MwDecodedVideoFrame = koffi.struct("MwDecodedVideoFrame", {
295
260
  data: "uint8_t *", // owned
296
261
  data_len: "size_t",
297
262
  });
298
- export const MwAudioDecodeConfig = koffi.struct("MwAudioDecodeConfig", {
299
- codec: "int32", // Opus only today
300
- sample_rate: "uint32",
301
- channels: "uint16",
302
- time_base: MwRational,
303
- });
263
+ // `MwAudioDecodeConfig` (Opus + AAC) is defined in pipeline-v7.ts.
304
264
  export const MwDecodedAudioFrame = koffi.struct("MwDecodedAudioFrame", {
305
265
  pts: "int64",
306
266
  duration: "uint64",
@@ -439,6 +399,8 @@ export const pipeline = {
439
399
  audioDecodeSessionFlush: pipelineLib.func("int mediaway_audio_decode_session_flush(void *session)"),
440
400
  audioDecodeSessionClose: pipelineLib.func("void mediaway_audio_decode_session_close(void *session)"),
441
401
  decodedAudioFrameFree: pipelineLib.func("void mediaway_decoded_audio_frame_free(MwDecodedAudioFrame *frame)"),
402
+ // ── ABI v7: streaming bytes, AAC decode config, capability probes (pipeline-v7.ts) ──
403
+ ...pipelineV7,
442
404
  // ── Capture-to-encode bridge (adr/pipeline/0005-capture-encode-bridge-c-abi.md) ──
443
405
  // Pushes one polled frame from a device.h capture handle straight into a
444
406
  // session — no intermediate MwCameraFrame/MwDesktopFrame, no extra copy.
@@ -457,6 +419,8 @@ export const device = {
457
419
  cameraClose: deviceLib.func("int mediaway_camera_capture_close(void *capture)"),
458
420
  cameraFrameFree: deviceLib.func("void mediaway_camera_frame_free(MwCameraFrame *frame)"),
459
421
  desktopConfigScreen: deviceLib.func("MwDesktopConfig mediaway_desktop_capture_config_screen(uint32_t output_index, MwRational time_base, MwGpuDeviceHandle gpu_device)"),
422
+ desktopConfigWindow: deviceLib.func("MwDesktopConfig mediaway_desktop_capture_config_window(uint64_t hwnd, MwRational time_base, MwGpuDeviceHandle gpu_device)"),
423
+ desktopBorderHidden: deviceLib.func("int mediaway_desktop_capture_border_hidden(void *capture, _Out_ bool *out_hidden)"),
460
424
  desktopOpen: deviceLib.func("int mediaway_desktop_capture_open(MwDesktopConfig *config, _Out_ void **out_capture)"),
461
425
  desktopGeometry: deviceLib.func("int mediaway_desktop_capture_geometry(void *capture, _Out_ uint32_t *out_width, _Out_ uint32_t *out_height)"),
462
426
  desktopPollFrame: deviceLib.func("int mediaway_desktop_capture_poll_frame(void *capture, _Out_ MwDesktopFrame *out_frame, _Out_ bool *out_has)"),
@@ -476,6 +440,12 @@ export const device = {
476
440
  gpuDeviceHandle: deviceLib.func("int mediaway_gpu_device_handle(void *device, _Out_ MwGpuDeviceHandle *out_handle)"),
477
441
  gpuDeviceClose: deviceLib.func("void mediaway_gpu_device_close(void *device)"),
478
442
  };
443
+ // ── Replay ring + payload placements (adr/container/0009) ────────────────────
444
+ // Declared in container-replay.ts (this file is near the 1000-line cap). Called here, after
445
+ // `MwRational` and `MwPacketView` exist, because the signatures name them.
446
+ export const replay = createReplayBindings(MwRational);
447
+ /** koffi's sizeof/offsetof for the replay structs — pinned by test/replay-ring.test.ts. */
448
+ export const replayStructLayout = () => replayLayout(replay.structs);
479
449
  // ── Copy helpers ───────────────────────────────────────────────────────────────
480
450
  /** Copy `len` bytes out of a koffi pointer/opaque handle. */
481
451
  export function copyBytes(ptr, len) {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Native library discovery + koffi load for @mediaway/ffi.
3
+ *
4
+ * Split out of index.ts (ADR-0024, multi-platform binding distribution)
5
+ * once the added per-platform search-path logic pushed index.ts's struct/
6
+ * function-binding definitions past the workspace's 1000-line source cap.
7
+ */
8
+ export declare function findLibrary(name: string): string;
9
+ export declare const containerLib: import("koffi").LibraryHandle;
10
+ export declare const pipelineLib: import("koffi").LibraryHandle;
11
+ export declare const deviceLib: import("koffi").LibraryHandle;
package/dist/loader.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Native library discovery + koffi load for @mediaway/ffi.
3
+ *
4
+ * Split out of index.ts (ADR-0024, multi-platform binding distribution)
5
+ * once the added per-platform search-path logic pushed index.ts's struct/
6
+ * function-binding definitions past the workspace's 1000-line source cap.
7
+ */
8
+ import koffi from "koffi";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ // ── Library discovery ──────────────────────────────────────────────────────────
13
+ // The cdylib is a Rust build artifact, not an installed system library.
14
+ // Search:
15
+ // 1. $MEDIAWAY_FFI_DIR
16
+ // 2. <this package>/native/<platform>-<arch> (libs for every platform
17
+ // bundled at pack time — the npm distribution; see § platform tag)
18
+ // 3. <repo root>/target/x86_64-pc-windows-gnu/debug (GNU toolchain, dev runs)
19
+ // 4. <repo root>/target/debug (host/MSVC toolchain)
20
+ // 5. cwd
21
+ const here = path.dirname(fileURLToPath(import.meta.url));
22
+ const repoRoot = path.resolve(here, "..", "..", "..", "..", "..");
23
+ /**
24
+ * `os-arch` tag matching one of `native/<tag>`'s subdirectories (this
25
+ * package bundles every platform's prebuilt lib directly — see
26
+ * tools/scripts/copy-native-dlls.ts). Returns `undefined` for an
27
+ * unsupported platform/arch pair rather than throwing, so callers can still
28
+ * fall through to the other search dirs (e.g. `$MEDIAWAY_FFI_DIR` during
29
+ * local development on an unlisted arch).
30
+ */
31
+ function platformTag() {
32
+ const os = process.platform === "win32" ? "win32" : process.platform === "linux" ? "linux"
33
+ : process.platform === "darwin" ? "darwin" : undefined;
34
+ const arch = process.arch === "x64" ? "x64" : process.arch === "arm64" ? "arm64" : undefined;
35
+ return os && arch ? `${os}-${arch}` : undefined;
36
+ }
37
+ const tag = platformTag();
38
+ const searchDirs = [
39
+ process.env.MEDIAWAY_FFI_DIR ?? "",
40
+ tag ? path.resolve(here, "..", "native", tag) : "",
41
+ path.join(repoRoot, "target", "x86_64-pc-windows-gnu", "debug"),
42
+ path.join(repoRoot, "target", "debug"),
43
+ process.cwd(),
44
+ ];
45
+ export function findLibrary(name) {
46
+ for (const dir of searchDirs) {
47
+ if (!dir)
48
+ continue;
49
+ const candidate = path.join(dir, name);
50
+ if (fs.existsSync(candidate))
51
+ return candidate;
52
+ }
53
+ throw new Error(`cannot find ${name}; set $MEDIAWAY_FFI_DIR or build the -ffi crates`);
54
+ }
55
+ /** cdylib filename Cargo produces for this platform. */
56
+ function libraryFilename() {
57
+ switch (process.platform) {
58
+ case "win32":
59
+ return "mediaway_ffi.dll";
60
+ case "linux":
61
+ return "libmediaway_ffi.so";
62
+ case "darwin":
63
+ return "libmediaway_ffi.dylib";
64
+ default:
65
+ throw new Error(`mediaway: unsupported platform ${process.platform}`);
66
+ }
67
+ }
68
+ function load(name) {
69
+ return koffi.load(findLibrary(name));
70
+ }
71
+ const libraryName = libraryFilename();
72
+ export const containerLib = load(libraryName);
73
+ export const pipelineLib = load(libraryName);
74
+ export const deviceLib = load(libraryName);
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Pipeline ABI v7 additions — `crates/mediaway-ffi/adr/pipeline/0007-stream-bytes-aac-decode-
3
+ * support-probe.md`: streaming fMP4 bytes, AAC decode, and the capability probes.
4
+ *
5
+ * Split out of index.ts for the workspace's 1000-line source cap. It imports only leaf modules
6
+ * (`rational.ts`, `loader.ts`), never `index.ts`, so `index.ts` can re-export it without a cycle.
7
+ *
8
+ * Ownership (from `pipeline.h`):
9
+ * - `mediaway_encode_session_poll_bytes` returns an OWNED buffer (release it with
10
+ * `pipeline.bufferFree`); nothing ready is `NULL` / `0` and needs no free.
11
+ * - `mediaway_encoder_support_at` returns an OWNED array; release it with
12
+ * `mediaway_encoder_support_free`, passing back the same pointer and count.
13
+ * - `mediaway_audio_decode_config_t.extra_data` is BORROWED, valid for `open()` only.
14
+ */
15
+ /** `mediaway_audio_decode_config_t` — Opus passes `extra_data = NULL`, AAC the raw ASC. */
16
+ export declare const MwAudioDecodeConfig: import("koffi").TypeObject;
17
+ /** `mediaway_encoder_capability_t` — one backend row of `mediaway_encoder_support_at`. */
18
+ export declare const MwEncoderCapability: import("koffi").TypeObject;
19
+ /** `mediaway_encoder_capability_t` as decoded by koffi. */
20
+ export interface RawEncoderCapability {
21
+ backend: number;
22
+ state: number;
23
+ path_class: number;
24
+ }
25
+ /** Codec numbers the probes and AAC decode accept (`mediaway_pipeline_codec_kind_t`). */
26
+ export declare const PIPELINE_CODEC: {
27
+ readonly h264: 0;
28
+ readonly hevc: 1;
29
+ readonly av1: 2;
30
+ readonly vp9: 3;
31
+ readonly aac: 4;
32
+ readonly opus: 5;
33
+ };
34
+ export declare const pipelineV7: {
35
+ audioDecodeConfigAac: import("koffi").Callable;
36
+ sessionPollBytes: import("koffi").Callable;
37
+ encoderSupportAt: import("koffi").Callable;
38
+ encoderSupportFree: import("koffi").Callable;
39
+ decoderSupport: import("koffi").Callable;
40
+ };
41
+ /**
42
+ * Size and field offsets of a registered struct, for pinning a mirror against the C header's
43
+ * layout in a test. `fields` are read in the order given.
44
+ */
45
+ export declare function structLayout(type: unknown, fields: readonly string[]): {
46
+ size: number;
47
+ offsets: Record<string, number>;
48
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Pipeline ABI v7 additions — `crates/mediaway-ffi/adr/pipeline/0007-stream-bytes-aac-decode-
3
+ * support-probe.md`: streaming fMP4 bytes, AAC decode, and the capability probes.
4
+ *
5
+ * Split out of index.ts for the workspace's 1000-line source cap. It imports only leaf modules
6
+ * (`rational.ts`, `loader.ts`), never `index.ts`, so `index.ts` can re-export it without a cycle.
7
+ *
8
+ * Ownership (from `pipeline.h`):
9
+ * - `mediaway_encode_session_poll_bytes` returns an OWNED buffer (release it with
10
+ * `pipeline.bufferFree`); nothing ready is `NULL` / `0` and needs no free.
11
+ * - `mediaway_encoder_support_at` returns an OWNED array; release it with
12
+ * `mediaway_encoder_support_free`, passing back the same pointer and count.
13
+ * - `mediaway_audio_decode_config_t.extra_data` is BORROWED, valid for `open()` only.
14
+ */
15
+ import koffi from "koffi";
16
+ import { pipelineLib } from "./loader.js";
17
+ import { MwRational } from "./rational.js";
18
+ /** `mediaway_audio_decode_config_t` — Opus passes `extra_data = NULL`, AAC the raw ASC. */
19
+ export const MwAudioDecodeConfig = koffi.struct("MwAudioDecodeConfig", {
20
+ codec: "int32", // Opus (5) or AAC (4)
21
+ sample_rate: "uint32",
22
+ channels: "uint16",
23
+ time_base: MwRational,
24
+ extra_data: "uint8_t *", // borrowed, open-call only; AAC's AudioSpecificConfig
25
+ extra_data_len: "size_t",
26
+ });
27
+ /** `mediaway_encoder_capability_t` — one backend row of `mediaway_encoder_support_at`. */
28
+ export const MwEncoderCapability = koffi.struct("MwEncoderCapability", {
29
+ backend: "int32",
30
+ state: "int32",
31
+ path_class: "int32", // meaningful only when state == SUPPORTED
32
+ });
33
+ /** Codec numbers the probes and AAC decode accept (`mediaway_pipeline_codec_kind_t`). */
34
+ export const PIPELINE_CODEC = { h264: 0, hevc: 1, av1: 2, vp9: 3, aac: 4, opus: 5 };
35
+ export const pipelineV7 = {
36
+ audioDecodeConfigAac: pipelineLib.func("MwAudioDecodeConfig mediaway_audio_decode_config_aac(uint32_t sample_rate, uint16_t channels, MwRational time_base, uint8_t *extra_data, size_t extra_data_len)"),
37
+ sessionPollBytes: pipelineLib.func("int mediaway_encode_session_poll_bytes(void *session, _Out_ uint8_t **out_data, _Out_ size_t *out_len)"),
38
+ encoderSupportAt: pipelineLib.func("int mediaway_encoder_support_at(int codec, uint32_t width, uint32_t height, _Out_ void **out_rows, _Out_ size_t *out_count)"),
39
+ encoderSupportFree: pipelineLib.func("void mediaway_encoder_support_free(void *rows, size_t count)"),
40
+ decoderSupport: pipelineLib.func("int mediaway_decoder_support(int codec, _Out_ int *out_state)"),
41
+ };
42
+ /**
43
+ * Size and field offsets of a registered struct, for pinning a mirror against the C header's
44
+ * layout in a test. `fields` are read in the order given.
45
+ */
46
+ export function structLayout(type, fields) {
47
+ // `type` stays `unknown` so callers can pass whatever `koffi.struct` returned; `koffi.d.ts`
48
+ // types the pair strictly (`TypeObject`), so narrow it once here.
49
+ const registered = type;
50
+ const offsets = {};
51
+ for (const field of fields)
52
+ offsets[field] = koffi.offsetof(registered, field);
53
+ return { size: koffi.sizeof(registered), offsets };
54
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `mediaway_rational_t`, in a leaf module.
3
+ *
4
+ * Split out of index.ts so `pipeline-v7.ts` (which needs it to define struct layouts that
5
+ * embed a timebase) can import it without a cycle: index.ts imports pipeline-v7.ts, so
6
+ * pipeline-v7.ts must not import index.ts.
7
+ */
8
+ export declare const MwRational: import("koffi").TypeObject;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `mediaway_rational_t`, in a leaf module.
3
+ *
4
+ * Split out of index.ts so `pipeline-v7.ts` (which needs it to define struct layouts that
5
+ * embed a timebase) can import it without a cycle: index.ts imports pipeline-v7.ts, so
6
+ * pipeline-v7.ts must not import index.ts.
7
+ */
8
+ import koffi from "koffi";
9
+ export const MwRational = koffi.struct("MwRational", {
10
+ num: "uint64",
11
+ den: "uint32",
12
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediaway/ffi",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "Rust-based, lightweight, fast — Mediaway native C ABI bindings for Node.js — the shared DLL behind the @mediaway packages",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",