node-labstreaminglayer
Version:
Node.js bindings for Lab Streaming Layer (LSL)
224 lines (200 loc) • 6.01 kB
text/typescript
import {
lsl_resolve_all,
lsl_resolve_byprop,
lsl_resolve_bypred,
lsl_create_continuous_resolver,
lsl_create_continuous_resolver_byprop,
lsl_create_continuous_resolver_bypred,
lsl_destroy_continuous_resolver,
lsl_resolver_results
} from './lib/index.js';
import { StreamInfo } from './streamInfo.js';
import { FOREVER } from './util.js';
/**
* Find all streams on the network.
* @param waitTime Time to wait for streams in seconds (default: 1.0)
* @returns Array of StreamInfo objects
*/
export function resolveStreams(waitTime: number = 1.0): StreamInfo[] {
// Create buffer for stream info pointers
const bufferSize = 1024;
const buffer = new Array(bufferSize).fill(null);
// Resolve streams
const numFound = lsl_resolve_all(buffer, bufferSize, waitTime);
// Convert to StreamInfo objects
const results: StreamInfo[] = [];
for (let i = 0; i < numFound; i++) {
const handle = buffer[i];
if (handle) {
results.push(new StreamInfo('', '', 0, 0, 0, '', handle));
}
}
return results;
}
/**
* Find streams by a specific property.
* @param prop Property name to match (e.g., 'name', 'type')
* @param value Property value to match
* @param minimum Minimum number of streams to find
* @param timeout Timeout in seconds
* @returns Array of StreamInfo objects
*/
export function resolveByProp(
prop: string,
value: string,
minimum: number = 1,
timeout: number = FOREVER
): StreamInfo[] {
// Create buffer for stream info pointers
const bufferSize = 1024;
const buffer = new Array(bufferSize).fill(null);
// Resolve streams by property
const numFound = lsl_resolve_byprop(
buffer,
bufferSize,
prop,
value,
minimum,
timeout
);
// Convert to StreamInfo objects
const results: StreamInfo[] = [];
for (let i = 0; i < numFound; i++) {
const handle = buffer[i];
if (handle) {
results.push(new StreamInfo('', '', 0, 0, 0, '', handle));
}
}
return results;
}
/**
* Find streams by an XPath predicate.
* @param predicate XPath predicate string (e.g., "name='MyStream' and type='EEG'")
* @param minimum Minimum number of streams to find
* @param timeout Timeout in seconds
* @returns Array of StreamInfo objects
*/
export function resolveByPred(
predicate: string,
minimum: number = 1,
timeout: number = FOREVER
): StreamInfo[] {
// Create buffer for stream info pointers
const bufferSize = 1024;
const buffer = new Array(bufferSize).fill(null);
// Resolve streams by predicate
const numFound = lsl_resolve_bypred(
buffer,
bufferSize,
predicate,
minimum,
timeout
);
// Convert to StreamInfo objects
const results: StreamInfo[] = [];
for (let i = 0; i < numFound; i++) {
const handle = buffer[i];
if (handle) {
results.push(new StreamInfo('', '', 0, 0, 0, '', handle));
}
}
return results;
}
// Legacy compatibility function
export function resolveStream(...args: any[]): StreamInfo[] {
if (args.length === 0) {
return resolveStreams();
} else if (typeof args[0] === 'number') {
return resolveStreams(args[0]);
} else if (typeof args[0] === 'string') {
if (args.length === 1) {
return resolveByPred(args[0]);
} else if (typeof args[1] === 'number') {
return resolveByPred(args[0], args[1]);
} else {
if (args.length === 2) {
return resolveByProp(args[0], args[1]);
} else {
return resolveByProp(args[0], args[1], args[2]);
}
}
}
throw new Error('Invalid arguments for resolveStream');
}
// FinalizationRegistry for automatic cleanup
const resolverRegistry = new FinalizationRegistry((obj: any) => {
try {
lsl_destroy_continuous_resolver(obj);
} catch (e) {
// Silently ignore cleanup errors
}
});
/**
* Continuously resolves streams in the background.
* Useful for monitoring streams that may appear and disappear.
*/
export class ContinuousResolver {
private obj: any; // Pointer to the continuous resolver object
constructor(
prop?: string,
value?: string,
pred?: string,
forgetAfter: number = 5.0
) {
if (pred !== undefined) {
if (prop !== undefined || value !== undefined) {
throw new Error(
'You can only either pass the prop/value argument or the pred argument, but not both.'
);
}
this.obj = lsl_create_continuous_resolver_bypred(pred, forgetAfter);
} else if (prop !== undefined && value !== undefined) {
this.obj = lsl_create_continuous_resolver_byprop(prop, value, forgetAfter);
} else if (prop !== undefined || value !== undefined) {
throw new Error(
'If prop is specified, then value must be specified too, and vice versa.'
);
} else {
this.obj = lsl_create_continuous_resolver(forgetAfter);
}
if (!this.obj) {
throw new Error('Could not create continuous resolver.');
}
// Register for automatic cleanup
resolverRegistry.register(this, this.obj, this);
}
/**
* Destroy the resolver and free resources.
*/
destroy(): void {
if (this.obj) {
try {
resolverRegistry.unregister(this);
lsl_destroy_continuous_resolver(this.obj);
} catch (e) {
// Silently ignore errors during destruction
}
this.obj = null;
}
}
/**
* Get the current list of resolved streams.
* @returns Array of StreamInfo objects
*/
results(): StreamInfo[] {
// Create buffer for stream info pointers
const bufferSize = 1024;
const buffer = new Array(bufferSize).fill(null);
// Get resolver results
const numFound = lsl_resolver_results(this.obj, buffer, bufferSize);
// Convert to StreamInfo objects
const results: StreamInfo[] = [];
for (let i = 0; i < numFound; i++) {
const handle = buffer[i];
if (handle) {
results.push(new StreamInfo('', '', 0, 0, 0, '', handle));
}
}
return results;
}
}