CP's Nice Player
Stream audio files inside VS Code with chunked, low-latency playback powered by FFmpeg and Web Audio.
Instead of transcoding an entire file before play starts, CP's Nice Player scans the source, builds a time-indexed manifest, and fetches ~2 second segments on demand. Playback begins quickly, seeking jumps to the right chunk, and memory stays bounded to a small sliding buffer.
Features
- Chunked streaming — Audio starts after the first segment is ready, not after a full-file transcode.
- Responsive seeking — Scrub to any position; only the chunks you need are fetched and decoded.
- Waveform overview — Ruler, waveform, and buffer state on one canvas, so what is loaded and what is playing are visible at a glance.
- Loop locators — Mark an in and out point to re-hear a passage, by dragging the ruler or with
[, ], \, and Shift+L.
- Seamless chunk joins — WSOLA-aligned linear crossfade blends overlap tails at chunk boundaries (configurable via
playback.crossfadeMs).
- Bounded memory — The webview keeps a configurable window of decoded PCM, not the whole track.
- Broad format support — MP3, WAV, OGG, Opus, FLAC, M4A, AAC, WebM, MP4, and MKV containers open in the custom editor (audio tracks only).
- Remote development — Playback works over Remote SSH, Dev Containers, WSL, and Codespaces via VS Code port forwarding.
- Configurable output — Stream as Ogg Vorbis (default) or FLAC, with tunable chunk size and prefetch depth.
Installation
Install from your editor's marketplace:
Or download the .vsix from GitHub Releases and install it with Extensions: Install from VSIX….
Getting started
Open a file
Supported files open in CP's Nice Player by default. You can also:
- Run CP's Nice Player: Open in CP's Nice Player from the Command Palette.
- Right-click a file and choose Open With… → CP's Nice Player.
To use a different editor for a file type, use Reopen Editor With… or set workbench.editorAssociations in Settings.
Requirements
- VS Code 1.90.0 or newer (or a compatible editor such as VSCodium on Open VSX).
- FFmpeg, on the machine where playback runs — which under Remote SSH, Dev Containers, WSL, or Codespaces is the remote host, not your local one. FFmpeg probes the source and transcodes playback chunks.
The extension looks for FFmpeg in this order: cp-nice-player.ffmpegPath (if set, it is the only candidate), then ffmpeg on PATH, then a build it downloaded itself.
On Linux, if neither of the first two turns anything up, the extension offers to download a pinned FFmpeg build into its own storage — useful on remote hosts and containers where there is no FFmpeg and no root to install one. The download is checksum-verified, and you can trigger it any time with CP's Nice Player: Download FFmpeg (Linux). See FFmpeg on the playback host.
On macOS and Windows, install FFmpeg yourself — brew install ffmpeg-full or winget install ffmpeg — and set cp-nice-player.ffmpegPath in user settings if it is not on PATH. On macOS, prefer ffmpeg-full over ffmpeg: Homebrew's slim formula ships without libvorbis, so the default ogg playback format falls back to mp3.
How it works
When you open a track:
- The extension starts a playback server on
127.0.0.1 in the environment where FFmpeg runs.
- It resolves the server address with
vscode.env.asExternalUri, which triggers VS Code port forwarding when the UI and server are not on the same host (Remote SSH, Dev Containers, WSL, Codespaces, etc.). The webview receives that external URI and fetches chunks through the forwarded port.
- The server scans audio frames and builds an index of ~2 s, frame-aligned chunks.
- The player fetches the index, then requests chunks around the playhead.
- Each chunk is decoded to PCM in the webview and written into an AudioWorklet ring buffer for continuous playback.
- At chunk seams, a WSOLA-aligned linear crossfade blends the overlap tail of one chunk with the head of the next.
- On seek, in-flight fetches are cancelled and buffering reprioritizes around the new position.
Extension settings
| Setting |
Default |
Description |
cp-nice-player.ffmpegPath |
(empty) |
Path to the ffmpeg executable on the playback machine. Machine-scoped (user settings only). Leave empty to use ffmpeg from PATH, or a build the extension downloaded — see FFmpeg on the playback host. |
cp-nice-player.playback.format |
ogg |
Preferred output format for streamed chunks: ogg (smaller, faster) or flac (lossless). If the preferred FFmpeg encoder is unavailable, the extension falls back to mp3 (when ogg is set) or wav (when flac is set). See Encode format resolution. |
cp-nice-player.playback.oggQuality |
6 |
libvorbis quality (0–10) when effective format is ogg. Also maps to libmp3lame quality when fallback is mp3. Higher is better quality and larger chunks. |
cp-nice-player.playback.chunkDurationSec |
2 |
Target duration of each streamed chunk in seconds (0.5–10). |
cp-nice-player.playback.crossfadeMs |
20 |
Per-chunk overlap tail length in milliseconds (0–500). Non-final chunks encode a short tail past the body boundary; the player crossfades it with the next chunk. Set to 0 to disable crossfade. |
cp-nice-player.playback.prefetchSec |
10 |
How far ahead of the playhead the player fetches chunks, in seconds of audio, counting the current chunk. The chunk count is derived as ceil(seconds / chunkDurationSec), at least 1 — at the 2 s chunk default, 10 → 5 chunks. |
cp-nice-player.playback.cachedIndexes |
100 |
How many recently opened media files keep their cached stream index in memory (minimum 1), so reopening one skips the FFmpeg frame scan. An index is metadata only — about 4.5 KB per minute of audio at the default chunk duration, so 100 typical songs cost under 2 MB. Cleared when the playback server restarts. |
cp-nice-player.playback.cachedChunksSec |
300 |
Seconds of already-fetched audio the player keeps cached — 5 minutes by default — so seeking back into it does not re-fetch. Unlike prefetchSec this spans both sides of the playhead. Converted to a chunk count the same way; at the 2 s chunk default, 300 → 150 chunks. |
cp-nice-player.playback.debugLogging |
false |
Log playback settings and transcode template at startup, plus per-request server activity, to the extension host console. |
Known limitations
- Audio only — Video tracks are not played; only the audio stream is handled.
- VS Code only — Streaming is served through VS Code's port forwarding to the extension's localhost server, not for external media players or standalone network deployment.
Development
Running tests
npm test runs the full suite:
| Command |
What it runs |
npm test |
Vitest media tests, then Mocha extension-host tests |
npm run test:media |
Vitest + jsdom — engine modules, StreamingAudioEngine, WorkletScheduler, player UI |
npm run test:extension |
Mocha + @vscode/test-cli — playback backend and playerPanel bridge |
FFmpeg on PATH is required for integration tests in the extension suite; suites skip automatically when FFmpeg is missing. The managed-download suite reaches GitHub for a few KB and skips when offline.
CI runs npm test on Linux and macOS for every push to main and every pull request. Both matter: the managed-install suites only have a pinned build to resolve on Linux and skip elsewhere.
Bumping the pinned FFmpeg build
node scripts/pin-ffmpeg.mjs regenerates src/ffmpegDownload/pins.ts from a BtbN release, verifying each archive against the release's own checksum manifest. See FFmpeg on the playback host.
| |