UNPKG

application-loopback

Version:
94 lines (75 loc) 3.41 kB
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process"; import { arch, platform } from "node:os"; import path from "node:path"; if (platform() !== "win32" || arch() !== "x64") { throw new Error("This package is currently only available for Windows 10 x64 and later"); } const loopbackBinaryPath = path.resolve(__dirname, "../", "bin", `${platform()}-${arch()}`, "ApplicationLoopback.exe"); const processListBinaryPath = path.resolve(__dirname, "../", "bin", `${platform()}-${arch()}`, "ProcessList.exe"); export type Window = { processId: string; title: string; } /** * Retrieves a list of active window process IDs and their titles by spawning an external binary. * @returns A promise that resolves to an array of `Window` objects, each containing a `processId` and a `title`. * @example * const windows = await getActiveWindowProcessIds(); * windows.forEach(win => { * console.log(win.processId, win.title); * }); */ export async function getActiveWindowProcessIds(): Promise<Window[]> { const cppProcess = spawn(processListBinaryPath, { detached: true, stdio: "pipe" }); cppProcess.stdout.setEncoding("utf8"); return new Promise<Window[]>((r) => { const processes: Window[] = []; cppProcess.stdout.on("data", (d: string) => processes.push( ...d.split("\n").map(x => { const [processId, title] = x.replace("\r", "").split(";"); if (processId && title) { return { processId, title }; } return undefined; }).filter(x => x !== undefined)) ) cppProcess.stdout.on("close", () => { r(processes) }) }); } const spawnedAudioCaptures: Map<string, ChildProcessWithoutNullStreams> = new Map(); /** * Starts capturing audio for a given process ID by spawning an external binary. * * @param processId - The unique identifier for the process whose audio should be captured. * @param options - Configuration options for the audio capture. * @param options.onData - Optional callback invoked with audio data as a `Uint8Array` whenever new data is available. * @throws {Error} If an audio capture for the specified `processId` is already running. * @returns The `processId` for which audio capture has started. */ export function startAudioCapture(processId: string, options: { onData?: (data: Uint8Array) => void }) { if (spawnedAudioCaptures.has(processId)) { throw new Error(`An audio capture with process id of ${processId} is already started`); } const cppProcess = spawn(`${path.resolve(__dirname, loopbackBinaryPath)}`, [processId], { detached: true, stdio: "pipe" }); spawnedAudioCaptures.set(processId, cppProcess) cppProcess.stdout.on("data", (d) => { options.onData?.(d); }) return processId; } /** * Stops the audio capture process associated with the given process ID. * @param processId - The unique identifier of the audio capture process to stop. * @returns `true` if the process was found and stopped, otherwise `false`. */ export function stopAudioCapture(processId: string): boolean { const cppProcess = spawnedAudioCaptures.get(processId); if (cppProcess) { cppProcess.kill() spawnedAudioCaptures.delete(processId); return true; } return false; }