libCEC Node.js API
    Preparing search index...

    libCEC Node.js API

    Pulse-Eight logo

    libCEC — Node.js binding

    A native N-API addon that binds libCEC through its C API (include/cecc.h), the same surface the .NET binding (src/dotnetlib) uses. It lets Node.js applications control CEC-capable HDMI devices — power a TV on/standby, become the active source, send remote keys, read device state — and receive bus events (log, key presses, commands, source changes, alerts).

    It works anywhere libCEC and a C++ toolchain do: Linux, macOS, Raspberry Pi and Windows, over the Pulse-Eight USB-CEC adapter or a SoC-native CEC backend.

    • libCEC installed with its development files. On Unix these are discovered via pkg-config (pkg-config --exists libcec) — on Debian/Ubuntu that is the libcec8-dev package; or build and make install this repository first.
    • Node.js ≥ 16 and a C++17 toolchain (node-gyp prerequisites: a compiler, make, and Python 3).
    npm install libcec
    

    The package carries sources, not a prebuilt binary: installing runs node-gyp rebuild, which compiles src/addon.cc against the libCEC already on the machine. The requirements above have to be met first, or the install fails at that step. Being built on the consumer's machine is also what lets one package serve every platform and every Node ≥ 16.

    cd src/nodejs
    npm install # runs node-gyp rebuild, compiling src/addon.cc against libcec
    node example/simple.js

    The compiled addon lands at build/Release/cec_native.node.

    Windows has no pkg-config, so tell node-gyp where libCEC's headers and its cec.lib import library are with two environment variables (defaults point at an installed USB-CEC Adapter SDK). From a repo build:

    set LIBCEC_INCLUDE_DIR=..\..\include
    set LIBCEC_LIB_DIR=..\..\build\Release\x64\lib
    npm install

    At runtime the addon needs cec.dll on the DLL search path — keep it next to cec_native.node (a .node resolves its dependencies from its own directory). The x64 Windows installer ships a prebuilt addon set up this way under its nodejs folder, so end users don't need a compiler; see docs/README.windows.md. The x86 installer does not — Node.js has had no 32-bit Windows build since v23, so there is no node.lib to link a 32-bit addon against.

    client/cec-client.js is an interactive REPL modelled on the C++ cec-client: the Node counterpart for exercising the library against real hardware and a worked example of every API call. Run it with npm run client (or node client/cec-client.js [port], or the installed cec-client-node bin):

    $ npm run client
    opening /dev/ttyACM0 ...
    connection opened. type 'h' for help, 'q' to quit.
    scan
    ...
    tx 1F:82:10:00 # transfer raw bytes
    on 0 # power on the TV
    pow 0 # query the TV's power status
    q

    node client/cec-client.js --help lists the command-line options (--list-devices, --info, --type, --monitor, --log-level, …).

    const cec = require('libcec'); // or require('.') from this folder

    const adapter = new cec.CecAdapter({
    deviceName: 'CECNode',
    deviceType: cec.CecDeviceType.RecordingDevice,
    });

    adapter.on('log', (m) => console.log(m.message));
    adapter.on('keyPress', (k) => console.log('key', cec.userControlKeyToString(k.keycode)));
    adapter.on('command', (c) => console.log('cmd', cec.opcodeToString(c.opcode)));

    adapter.open(); // or adapter.open(port) with a path from detectAdapters()

    adapter.powerOnDevices(cec.CecLogicalAddress.TV);
    for (const addr of adapter.getActiveDevices())
    console.log(cec.logicalAddressToString(addr), adapter.getDeviceOSDName(addr));

    adapter.close();

    CecAdapter is an EventEmitter. libCEC fires its callbacks from its own worker thread; the addon marshals each one onto the Node event loop via a ThreadSafeFunction, so handlers run on the main thread like any other event.

    event argument(s) source callback
    log { message, level, time } logMessage
    keyPress { keycode, duration } keyPress
    command { initiator, destination, opcode, parameters, ack, eom, opcodeSet, transmitTimeout } commandReceived
    sourceActivated (logicalAddress, activated) sourceActivated
    alert alert (a CecAlert) alert
    configurationChanged { deviceName, deviceTypes, physicalAddress, logicalAddresses, cecVersion, adapterType, firmwareVersion, … } configurationChanged
    menuStateChanged state (a CecMenuState) menuStateChanged
    commandHandler same shape as command commandHandler (opt-in)

    commandHandler is off by default — pass { enableCommandHandler: true } to the constructor to receive it. It routes every command through libCEC's blocking command-handler path and carries the same data as the cheaper command event, so it's only worth enabling if you specifically need that hook.

    Lifecycle: open(port, timeout=10000), close().

    Control: transmit(command), powerOnDevices(address), standbyDevices(address), setActiveSource(deviceType), setInactiveView(), volumeUp(), volumeDown(), muteAudio(), sendKeypress(destination, key, wait=true), sendKeyRelease(destination, wait=true), setOSDString(destination, duration, message).

    Queries: getActiveSource(), isActiveSource(address), getDevicePowerStatus(address), getDeviceVendorId(address), getDevicePhysicalAddress(address), getDeviceCecVersion(address), getDeviceOSDName(address), getActiveDevices(), pollDevice(address), rescanDevices(), pingAdapters(), detectAdapters(), getLibInfo().

    Enum helpers (module-level): cecVersionToString, powerStatusToString, logicalAddressToString, vendorIdToString, opcodeToString, userControlKeyToString, plus the enum tables (CecLogicalAddress, CecDeviceType, CecPowerStatus, CecUserControlCode, CecOpcode, …).

    • commandHandler and menuStateChanged cannot suppress libCEC's default handling from JavaScript. Both are dispatched on libCEC's callback thread and expect a synchronous "handled?" return; the addon always answers "not handled" so the library keeps its default behaviour. Honouring a JS return would mean blocking that thread on the Node event loop and racing libCEC's 1000ms timeout. They are therefore exposed as observe-only events.
    • Call close() when done. It stops libCEC's worker thread before releasing the thread-safe callbacks, so no event can fire against a torn-down adapter.

    GPL-2.0-or-later, or a commercial licence from Pulse-Eight — the same dual licence as libCEC itself.