npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

electron-vlc-player

v1.0.2

Published

Embed libVLC in Electron with built-in overlay controls

Downloads

297

Readme

Electron VLC Player

English | 中文

Embed libVLC in Electron pages with a simpler integration path and broader video format support.

[!NOTE] Page scrolling is not supported; the player is intended for a fixed layout region.

Install

npm install electron-vlc-player

The postinstall script compiles the native module (vlc_binding.node) for your project's Electron version. Install platform C++ build tools first (see “Native module” below).

Usage

import { BrowserWindow } from "electron";
import { VlcPlayer } from "electron-vlc-player";

const win = new BrowserWindow({ width: 1280, height: 720 });
await win.loadFile("index.html");

const player = new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir: "/path/to/libvlc",
});

await player.embed();
player.setSource("/path/to/video.mkv");
player.on("playing", () => console.log("playing"));
player.setRate(1.25);

Requirements

Operating systems

| Platform | Minimum (recommended) | Notes | | ----------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Windows | Windows 10 or later | | | macOS | macOS 11 Big Sur or later | | | Linux | Ubuntu 20.04 / Debian 11 / Fedora 34 or equivalent (glibc ≥ 2.31) | Embedding requires X11 (libX11); pure Wayland is untested — use XWayland in practice. |

Electron / Node.js

| Item | Requirement | | ------------ | --------------- | | Electron | >= 28.0.0 | | Node.js | >= 18.0.0 |

Native module

This package is Electron-only. The npm tarball does not ship a prebuilt vlc_binding.node.

Automatic build: On npm install, the binding is compiled locally for your Electron target architecture (same as process.arch).

Manual rebuild (when postinstall fails, after upgrading Electron, etc.):

# In your app project root
npx electron-rebuild -f -w electron-vlc-player

# When developing this library at the repo root
npm run rebuild

Set SKIP_EVP_NATIVE_REBUILD=1 to skip automatic rebuild in the install script.

You also need:

  • Platform C++ build tools (see table below)
  • libVLC built for the same architecture as your Electron app (e.g. arm64 VLC on Apple Silicon)

| Platform | Build dependencies | | ----------- | -------------------------------------------------------------------- | | Windows | Visual Studio Build Tools (“Desktop development with C++”), Python 3 | | macOS | Xcode Command Line Tools | | Linux | build-essential, libx11-dev, Python 3 |

Which platform/arch combinations work in practice depends on Electron’s official support and whether you can supply a matching libVLC for that arch.

libVLC runtime and vlcDir

[!IMPORTANT] This package does not ship libVLC. You must pass a libVLC root directory via vlcDir (the library does not auto-configure it).

[!NOTE] VLC 3.0.x only for now (VLC 4.x embedding is still unstable).

Use probeDefaultVlcDir() to detect common VLC install paths on each platform, then pass the result to VlcPlayer:

import { VlcPlayer, probeDefaultVlcDir } from "electron-vlc-player";

const vlcDir = probeDefaultVlcDir();
if (!vlcDir) throw new Error("Install VLC or set vlcDir manually");

new VlcPlayer({ window: win, container: "#player", vlcDir });
  • Option 1: user installs VLC; use probeDefaultVlcDir() or a manual path as vlcDir
  • Option 2: bundle libVLC with your app and pass its relative path inside the app as vlcDir (no separate VLC install, larger app size)

The vlcDir folder must contain the platform libVLC library and a plugins directory.

Windows

<vlcDir>/
  libvlc.dll
  libvlccore.dll
  plugins/

macOS

Point vlcDir at Contents/MacOS (sibling of plugins/). VLC 3.0.x installers often place dylibs under lib/; the library resolves that automatically. probeDefaultVlcDir() / resolveVlcDir() also accept VLC.app, MacOS, or MacOS/lib paths.

/Applications/VLC.app/Contents/MacOS/
  lib/
    libvlc.dylib
    libvlccore.dylib
  plugins/

(Some older layouts may still place libvlc.dylib directly in MacOS/.)

Linux

Often a system package path (directory containing libvlc.so) with plugins accessible:

/usr/lib/x86_64-linux-gnu/   # or /usr/lib64
  libvlc.so

Plugins are commonly under /usr/lib/vlc/plugins/. If vlcDir has no plugins subfolder, point vlcDir at a root that contains both libvlc.so and plugins/, or bundle the same vlcDir/plugins layout as on Windows.

Examples

import { VlcPlayer } from "electron-vlc-player";

// Windows
new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir: "C:\\Program Files\\VideoLAN\\VLC",
});

// macOS
new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir: "/Applications/VLC.app/Contents/MacOS",
});

// Linux
new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir: "/usr/lib/x86_64-linux-gnu",
});

[!NOTE] To change vlcDir or hardware acceleration (hardwareAcceleration) at runtime, call destroy() first, then create a new VlcPlayer with the new options.

Integration notes

[!WARNING] On Windows, do not call app.disableHardwareAcceleration() at app startup. It conflicts with this library’s transparent overlay: Chromium falls back to software compositing, fully transparent regions may become click-through to the underlying libVLC child window, so only the bottom bar receives clicks while the video area does not.

[!WARNING] The control bar is a child BrowserWindow of the main window. When a system file dialog is open, if the overlay child is still visible and receives activation, Electron/Win32 may raise that child so the bar covers the dialog.

Hide the overlay before opening a file dialog, then restore it:

player.hideOverlay();
try {
  const result = await dialog.showOpenDialog(mainWindow, {
    properties: ["openFile"],
  });
  // ...
} finally {
  player.showOverlay();
  // Restore keyboard focus:
  player.focusOverlay({ stealWindowFocus: true });
}

[!NOTE] macOS — overlay hover to show controls: Unfocused windows do not receive DOM mouseenter on macOS (unlike Windows). While the overlay is visible, the library compensates via main-process cursor polling; when another app fully covers the video region, controls are not shown spuriously. For playlist switches and other main-window UI actions, do not call focusOverlay() — it activates the child window and deactivates the main window (grey traffic-light buttons).

API

VlcPlayer

VlcPlayer extends LibVLC (libVLC playback API + events). After embed() you can call player.play(), player.setSource(), player.on('playing'), etc. directly.

Embedding and layout

  • embed() / isEmbedded() / destroy()
  • setContainer() / setPageFullscreen() / setFullScreen() (and isPageFullscreen() / isFullScreen())
  • hideOverlay() / showOverlay() / focusOverlay(options?) — does not steal main-window focus by default; { stealWindowFocus: true } only when overlay keyboard shortcuts are needed (see “Integration notes”)

libVLC playback (after embed())

| Category | Methods | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Media | setSource, getMediaInfo, parseMedia, getMediaMetadata / getMediaMetadataResult, getMediaTracks / getMediaTracksResult, unloadMedia | | Playback | play, pause, stop, togglePause, setPaused, isPlaying, getState, getRate, setRate, setPlaylist, playPrevious, playNext, hasPrevious, hasNext, getPlaybackMode, setPlaybackMode | | Progress | getTime, setTime, getLength, getPosition, setPosition, isSeekable | | Volume | getVolume, setVolume, toggleMute, getMute, setMute, getAudioDelay, setAudioDelay | | Tracks | getAudioTracks, getAudioTrack, setAudioTrack, getSubtitleTracks, getSubtitleTrack, setSubtitleTrack, addSubtitleFile, getVideoTracks, getVideoTrack, setVideoTrack | | Video | getScale, setScale, getAspectRatio, setAspectRatio, setCropGeometry, getVideoSize, takeSnapshot | | Chapters | getChapter, setChapter, nextChapter, previousChapter, getTitleDescriptions, etc. | | Other | navigate, setVlcFullscreen, setRole, getFps, hasVout |

Constants

VlcState, VlcNavigate, VlcRole, VlcEvent, VlcEventName.

mediaOptions

player.setSource("https://example.com/live.m3u8", {
  mediaOptions: [":network-caching=3000"],
});

Metadata, tracks, and duration

Use parseMedia() for metadata, tracks, and duration.

libVLC 3 reads track tables via libvlc_media_tracks_get (or legacy libvlc_media_get_tracks_info); calling while playing may be unstable — the library may fall back to switchable track lists.

player.setSource("/path/to/video.mp4");
player
  .parseMedia()
  .then(({ metadata, tracks, tracksCode, tracksMessage, length, info }) => {
    console.log(metadata.title, tracks, length);
  })
  .catch((err) => console.error(err));

tracks lists streams in the file (including width/height per video track); which track is active uses getVideoTrack() / getAudioTrack() / getSubtitleTrack() (below).

Selected tracks (libVLC track ids matching get*Tracks(); subtitles off = -1):

const videoId = player.getVideoTrack();
const audioId = player.getAudioTrack();
const subId = player.getSubtitleTrack(); // -1 = no subtitles

const video = player.getVideoTracks().find((t) => t.id === videoId);
const audio = player.getAudioTracks().find((t) => t.id === audioId);

While playing, getVideoSize() and getFps() return current decoded resolution and frame rate.

After parse, getMediaMetadataResult() returns metadata; while playing, getMediaTracksResult() returns file streams.

[!WARNING] Do not read media track tables while paused (can crash). Prefer during playback or after stop.

const { data: meta, code, message } = player.getMediaMetadataResult();
const {
  data: tracks,
  code: tracksCode,
  message: tracksMessage,
} = player.getMediaTracksResult();
// If not parsed, `code` is MEDIA_NOT_PARSED; non-empty `code` means incomplete data — map `code` to your UI strings
probeMedia() — without loading into the player

parseMedia() applies to the current setSource media. To pre-read duration/basic metadata for a playlist entry without switching playback, use package-level probeMedia():

  • Does not change current source or start playback
  • Requires initialized libVLC (e.g. after VlcPlayer.embed())
  • Returns MediaProbeResult: parsed, length (ms, 0 if unknown), metadata (title / artist / album / genre)
  • No track list, resolution, or fps — use parseMedia() or getVideoSize() / getFps() after playback
import { probeMedia } from "electron-vlc-player";

// Probe playlist entries in the background (queue yourself to avoid blocking UI)
const { parsed, length, metadata } = probeMedia("/path/to/video.mkv", 15_000);
if (parsed && length > 0) {
  console.log(metadata.title, length);
}

Utilities

  • probeMedia(src, timeoutMs?) — parse duration and basic metadata without loading into the player (requires init libVLC); returns MediaProbeResult
  • probeDefaultVlcDir() — optionally detect common VLC / libVLC install paths (string | null)
  • resolveVlcDir(vlcDir) — validate and normalize libVLC directory
  • getHardwareAcceleration() / setHardwareAcceleration(mode) — query or set process-wide --avcodec-hw
  • normalizeHardwareAcceleration(mode) — validate HW acceleration value
  • probeDefaultFfmpegPath() — optionally detect common ffmpeg paths and PATH (string | null)
  • resolveFfmpegExecutable(path) — validate ffmpeg executable
  • getLibVlcVersion() — queries libVLC version
  • getBinding() — returns the full VlcBinding (Low-level binding)

Events

const onPlaying = () => console.log("playing");
player.on("playing", onPlaying);
player.off("playing", onPlaying);

player.on("endReached", (ev) => console.log("ended", ev.time));

Common events: playing | paused | stopped | endReached | timeChanged | positionChanged | lengthChanged | buffering | error | playlistItemChanged (see VlcEventName).

Track changes

Control bar audio/subtitle menus or set*Track:

player.on("audioTrackChanged", ({ trackId }) => {
  /* current audio track id */
});
player.on("videoTrackChanged", ({ trackId }) => {
  /* current video track id */
});
player.on("subtitleTrackChanged", ({ trackId }) => {
  /* trackId === -1 means subtitles off */
});

Hardware acceleration

libVLC accepts --avcodec-hw= for decode hardware acceleration.

| Value | Description | | ------------------- | ------------------------------------ | | none | Disable HW decode (default) | | any | Let libVLC pick an available backend | | d3d11va / dxva2 | Common on Windows | | vaapi / drm | Common on Linux | | videotoolbox | macOS |

import {
  VlcPlayer,
  getHardwareAcceleration,
  setHardwareAcceleration,
} from "electron-vlc-player";

const player = new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir: "C:\\Program Files\\VideoLAN\\VLC",
  hardwareAcceleration: "d3d11va",
});

console.log(getHardwareAcceleration()); // "d3d11va"

// Changing mode requires a new instance
await player.destroy();
setHardwareAcceleration("any");
const player2 = new VlcPlayer({ window: win, container: "#player", vlcDir });

Localization

Built-in control bar, track menus, subtitle dialogs, and related overlay strings support multiple locales.

  • Constructor option locale (optional, BCP 47, e.g. zh-CN, en, ja): when omitted, follows Electron / system locale; falls back to English if no built-in pack matches.
  • SUPPORTED_PLAYER_LOCALES: list of supported locale ids.
  • Runtime getLocale() / setLocale(locale): switch control bar language (updates the overlay).
import { VlcPlayer, SUPPORTED_PLAYER_LOCALES } from "electron-vlc-player";

console.log(SUPPORTED_PLAYER_LOCALES); // ['en', 'zh-CN', 'zh-TW', 'ja', ...]

const player = new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir,
  locale: "zh-CN",
});

player.setLocale("ja");

Control bar

Playlist and previous/next controls

The built-in control bar can show Previous / Next beside the play button.

  • Pass paths/URLs via constructor playlist: string[] or setPlaylist(paths) (independent of your own UI list — sync the same array to enable buttons).
  • Buttons appear only with at least 2 items; hidden with 0–1 items.
  • playPrevious() / playNext() switch within the list; hasPrevious() / hasNext() indicate availability.
  • Default autoAdvancePlaylist: true: on endReached, plays the next item if any (playNext()).
  • playbackMode (default default): end-of-item behavior — loop (list), repeat (single item); use getPlaybackMode() / setPlaybackMode().
  • Switching via control bar or API emits playlistItemChanged ({ src, index }) for title/sidebar updates; the player already called setSource — listeners need not call it again.
player.setPlaylist(["/a.mkv", "/b.mkv", "/c.mkv"]);
player.setPlaybackMode("loop"); // or "repeat" | "default"
player.on("playlistItemChanged", ({ src, index }) => {
  console.log("now playing", index, src);
});

page-fullscreen button

  • Constructor option pageFullscreenButton (default true): set to false to hide the page-fullscreen button.

Seek-bar hover preview

The control bar can offline-generate hover preview sprites for local files (similar to streaming sites). Network URLs / streams cannot generate or show hover previews.

[!NOTE] VLC 3.x has no reliable background snapshot API at arbitrary times. An invisible second player was tried but worked poorly, so ffmpeg is used to build preview sprites.

Generation time depends on resolution; 1080p and below is usually acceptable; 2K/4K can be slow.

Requirements:

  1. Media is a local path or file:// (see isLocalMediaSource())
  2. Pass ffmpegPath when constructing VlcPlayer, or call setFfmpegPath later (not auto-detected)

Use probeDefaultFfmpegPath() to help find ffmpeg:

new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir: probeDefaultVlcDir() ?? "/path/to/libvlc",
  ffmpegPath: probeDefaultFfmpegPath() ?? undefined, // or "C:\\ffmpeg\\bin\\ffmpeg.exe"
  seekPreviewCacheDir: "D:/cache/evp-preview", // optional, default %TEMP%/evp-preview
});

Without a valid ffmpeg path, the Generate preview button is hidden.

Flow:

  1. Open a local file, click Generate preview on the control bar (shows progress; cannot click again while running)
  2. ffmpeg decodes once in order (tries -hwaccel auto, falls back to software), outputs sprite.jpg via fps → scale → tile
  3. After completion, hovering the progress bar shows previews instantly

Cache layout (default %TEMP%/evp-preview, shared globally):

{seekPreviewCacheDir}/{mediaKey}/index.json
{seekPreviewCacheDir}/{mediaKey}/sprite.jpg

| API | Description | | ------------------------------------------------------------------------------------ | ------------------------------------------------ | | ffmpegPath (option) / setFfmpegPath / getFfmpegPath | ffmpeg executable path (process-wide) | | seekPreviewCacheDir (option) / setSeekPreviewCacheDir / getSeekPreviewCacheDir | Preview sprite cache root (process-wide) | | clearSeekPreviewCacheDir() | Cancel in-flight generation and clear cache root | | generateSeekPreviewSprite() | Generate sprite for current local media | | cancelSeekPreviewSprite() | Cancel generation (removes .work/ only) |

Changing media or calling destroy() does not delete finished sprites; call clearSeekPreviewCacheDir() or delete directories manually. If sprites exist on disk but ffmpeg is not configured: button hidden, hover still uses cache.

Long videos use adaptive spacing (~300 frames max): intervalMs = max(2000, ceil(durationMs/300/1000)*1000).

import {
  VlcPlayer,
  probeDefaultVlcDir,
  probeDefaultFfmpegPath,
} from "electron-vlc-player";

const player = new VlcPlayer({
  window: win,
  container: "#player",
  vlcDir: probeDefaultVlcDir() ?? "/path/to/libvlc",
  ffmpegPath: probeDefaultFfmpegPath() ?? undefined,
  seekPreviewCacheDir: "D:/cache/evp-preview", // optional
});

player.on("seekPreviewError", (err) => console.error(err));
player.generateSeekPreviewSprite(); // or use control bar button

Custom overlay context menu

Coordinates relative to overlay client area:

import { Menu } from "electron";

player.on("overlayContextMenu", () => {
  Menu.buildFromTemplate([
    { label: "Copy path", click: () => console.log(player.getSource()) },
  ]).popup({ window: win });
});

Built-in shortcuts

When the overlay has focus, mouse and keyboard shortcuts are available.

Mouse

| Action | Effect | | ------------ | ----------------- | | Mouse wheel | Volume ±1% | | Click | Play / pause | | Double-click | Toggle fullscreen |

Keyboard

| Key | Effect | | ------------------- | --------------------------------- | | Space | Play / pause | | T | Toggle page fullscreen | | F | Toggle fullscreen | | Esc | Exit fullscreen / page fullscreen | | / | Seek −10s / +10s | | Shift + / | Seek −3s / +3s | | Ctrl + / | Seek −1m / +1m | | Alt + / | Seek −1 / +1 frame | | / | Volume +10% / −10% |

Source layout

src/
  index.ts                 # Public exports
  types.ts                 # Public TypeScript types (MediaQueryResult, etc.)
  native.ts                # N-API binding load, initLibVlc, probeMedia
  vlc-path.ts              # vlcDir detection and validation
  vlc-constants.ts         # libVLC state / event / track constants
  vlc-event-names.ts       # Event name mapping
  hardware-acceleration.ts
  seek-preview-sprite.ts   # Seek-bar preview sprites (ffmpeg)
  overlay-path.ts          # Resolve overlay assets after build
  i18n/                    # Control bar locales
  libvlc/
    LibVLC.ts              # libVLC media player API (parse / tracks / playback)
    index.ts
  vlc-player/              # Electron embedding (controllers)
    VlcPlayer.ts           # Main entry, extends LibVLC
    layout.ts              # Window / container layout and native embed
    playlist.ts            # Playlist and loop / repeat
    overlay/               # Transparent overlay window
    seek.ts, ipc.ts, …
overlay/                   # Built-in controls (HTML/CSS/JS, loaded in overlay)
example/src/               # Electron example (main process modules)

VlcPlayer extends LibVLC: embedding and UI live in vlc-player/; playback and media parsing in libvlc/.

How It Works

The player stacks three layers inside your Electron window:

How It Works

| Layer | Role | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Page container element | A DOM element in your renderer page (container, e.g. #stage). The library measures its bounds and anchors both the video surface and overlay to this region. | | libVLC embed layer | A native child window created by the N-API binding. libVLC decodes and renders video into this surface. | | Overlay control layer | A transparent child BrowserWindow aligned with the container, loaded with built-in HTML/CSS/JS controls. User input and state updates are bridged to the main process via IPC. |

Call embed() after the page loads to create and align these layers. Container resize, page fullscreen, and window fullscreen are handled automatically.

License

MIT