Skip to content

Releases: BlackBlazent/blackvideo-mini-player

BlackVideo Mini Player v2.4.0 — Standalone Mode, Sprite Preview, LLM Captions, Whisper Fix, Progress %, Auto-Updater

Choose a tag to compare

@LoneStamp LoneStamp released this 13 Mar 02:41

✅ What's in This Release


v2 4 0

🖥️ Standalone Mode — Open Without a Video

The player can now be launched with no arguments and shows a welcome screen instead of exiting.

Before (v2.3.0):

blackvideo-player.exe          ← exits immediately or crashes
blackvideo-player.exe video    ← only way to use it

After (v2.4.0):

blackvideo-player.exe          ← opens welcome screen
  • Welcome screen renders inside the SDL2 window: large icon, "Open a video or drop a file here" prompt, and an Open File button in the centre — all drawn in C via ui_overlay.c
  • Drag and drop: drop any video file onto the window at any time (welcome screen or during playback) — handled via SDL_DROPFILE event, requires SDL_EventState(SDL_DROPFILE, SDL_ENABLE) at init
  • player.ads gains a No_Video state alongside Playing / Paused / Stopped
  • main.adb no longer exits on missing argument — passes "" to Player.Run which enters No_Video state
  • Right-click Open Video File and the Open button both work from the welcome screen

How it works in Ada + C:

-- player.ads
type Player_State is (No_Video, Stopped, Playing, Paused);

-- player.adb: Run() — no argument guard removed
if Video_File = "" then
   State := No_Video;
   UI_Overlay.Set_No_Video_Mode (True);
end if;
// ui_overlay.c: draw welcome screen when g_no_video = 1
void bv_ui_draw(...) {
    if (g_no_video) { draw_welcome_screen(ren, w, h); return; }
    ...
}

🖼️ Sprite Thumbnail Preview on Hover

Hovering the seek bar shows a small thumbnail of the video at that position, with a timestamp label beneath it.

How it works:

When a video opens, a background cmd.exe /c job (same WinExec pattern as Whisper) runs:

ffmpeg -i video.mp4 -vf "fps=1/10,scale=160:-1" -q:v 5
       build\cache\videoname_%04d.jpg

This extracts one frame every 10 seconds as 160px-wide JPEG thumbnails and stores them in build\cache\<video_hash>\. On a 90-minute film that is ~540 small JPEGs (~2–3 MB total), generated in roughly 10–20 seconds in the background.

Hover rendering:

  • ui_overlay.c tracks mouse X over the seek bar and computes hover_pos = duration * (mouse_x - bar_x) / bar_w
  • Frame index = hover_pos / 10 → loads the matching JPEG via SDL_image (or stb_image — no extra DLL)
  • Renders a 160×90 popup box above the scrubber with the frame and a timestamp label below it
  • Popup disappears when the mouse leaves the seek bar area

New files:

  • src/thumb_cache.ads / .adb — Ada module: hash video path, check cache dir, launch ffmpeg job, map hover position → JPEG filename
  • csrc/thumb_preview.c — C: SDL_Texture loading (via stb_image.h, header-only), draw popup box

Cache location: build\cache\<8-char-hash-of-video-path>\frame_%04d.jpg
Cleared by right-click → Clear Thumbnail Cache (new menu item).


⚡ Whisper Performance Fix — Why It Was So Slow

Root cause (the real problem):

whisper_bridge.adb's Generate() function calls C_System() (the C system() function) to run both ffmpeg and whisper-cli. C_System is fully synchronous — it blocks the calling thread until the command exits. But Launch_Whisper in player.adb calls it via WinExec with cmd.exe /c "ffmpeg ... && whisper-cli ...".

WinExec is nominally asynchronous but its behaviour with cmd.exe /c "..." on modern Windows is unreliable — the cmd shell can hold the process slot open or Windows Defender / real-time antivirus scans the newly created .wav and .srt files before releasing them, causing the job to appear stalled for minutes. Additionally, whisper-cli with the ggml-base model runs on CPU only and is genuinely slow: expect ~0.3× real-time on a mid-range CPU (a 60-second video takes ~3–5 minutes, not hours — something else is wrong).

The real culprits causing "hours":

  1. Windows Defender scans every .wav and .srt file whisper-cli writes. On some machines this causes 100% disk hold for the duration.
  2. WinExec + cmd.exe /c with a long quoted string — Windows has a 8191-char limit on cmd.exe command lines. If the video path is long, the command silently truncates and whisper-cli never starts — but WinExec returns success (≥ 32) and Whisper_Running stays True forever, showing "generating..." permanently.
  3. The ggml-base model is genuinely slow for long content. A 2-hour film can take 30–60 minutes on CPU.

Fixes in v2.4.0:

  • Replace WinExec with CreateProcess — proper async process launch with a PROCESS_INFORMATION handle we keep alive and poll with GetExitCodeProcess(hProcess, &code); code == STILL_ACTIVE. This is reliable on all Windows versions and not affected by the cmd.exe quoting issues.
  • Write a sentinel .done file — at the end of the whisper batch, echo done > <out>.done. Check_Whisper_Done polls for the .done file, not the .srt, so we know the SRT is fully written before loading it.
  • Path length guard — if the full command string exceeds 7500 chars, the batch is written to a temp .bat file in %TEMP% and CreateProcess runs cmd.exe /c that.bat instead. No truncation.
  • Timeout detection — if Whisper_Running has been true for > 30 minutes with no .done file, the status banner changes to Whisper: stalled? Check Defender / console and a [Whisper] TIMEOUT message is printed to console.
  • Better model adviceWHISPER_SETUP.md updated with the Windows Defender exclusion path recommendation.

New Ada/C additions:

-- player.adb
Whisper_H_Process  : System.Address := System.Null_Address;
Whisper_Start_Tick : Interfaces.C.unsigned := 0;

function CreateProcess (...) return Boolean
with Import, Convention => C, External_Name => "bv_create_process";

-- csrc/process_helpers.c
int bv_create_process(const char *cmd, HANDLE *hProcess_out);
int bv_process_still_running(HANDLE hProcess);
void bv_close_handle(HANDLE h);

📊 Whisper / LLM Progress Percentage

Instead of a static Whisper: generating..., the status banner now shows a live percentage:

Whisper: generating...  23%   [████████░░░░░░░░░░░░░░░░░░░░░░]

How whisper-cli outputs progress:
whisper-cli writes progress lines to stderr in the form:

whisper_print_progress_callback: progress = 23%

Implementation:

  • bv_create_process is replaced with bv_create_process_with_pipe which creates a read pipe on stderr of the child process (using CreatePipe + STARTUPINFO.hStdError = write_end)
  • Each frame, Check_Whisper_Progress calls PeekNamedPipe (non-blocking) to read any available stderr bytes, scans for the progress = NN% pattern, and calls UI_Overlay.Set_Whisper_Status("Whisper: generating... NN%")
  • The C overlay draws a mini progress bar beside the text in the banner

Same pipe approach applies to LLM providers that stream token output (see below).


🤖 LLM Cloud Caption Generation

Six cloud providers available from the context menu — right-click → Generate Captions (LLM) ▶ → sub-menu:

Provider Model used Notes
Claude (Anthropic) claude-sonnet-4-5 Audio via base64 or transcript
OpenAI gpt-4o-audio-preview Native audio input
Gemini (Google) gemini-1.5-pro Audio file upload
DeepSeek deepseek-chat Text transcript → SRT
Grok (xAI) grok-2 Text transcript → SRT

Architecture — two-step approach (no audio upload for text-only providers):

  1. Run whisper-cli locally for a fast draft transcript (text only, no SRT timing) — takes ~30 seconds for a 60s clip with ggml-tiny
  2. Send the transcript text + video metadata to the LLM with a prompt: "Convert this transcript to a properly timed SRT subtitle file. Duration: Xs. Speaker count: N."
  3. The LLM returns a formatted .srt which is auto-loaded into Track 2

For OpenAI and Gemini which support native audio, step 1 is skipped and the WAV is uploaded directly.

API key storage — %APPDATA%\BlackVideo\keys.cfg:

ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GEMINI_API_KEY=AIza...
DEEPSEEK_API_KEY=...
GROK_API_KEY=...

First use: context menu shows Set API Key for [Provider] — a small SDL2 text input overlay appears (drawn in C). Keys are written to keys.cfg and reused on subsequent sessions.

HTTP in Ada + C:

  • csrc/llm_client.c — uses WinINet (available on all Windows, no extra DLL) for HTTPS POST. No libcurl dependency.
  • src/llm_bridge.ads / .adb — Ada wrapper: Send_Request(Provider, Transcript, Duration) → returns raw SRT string
  • Streaming responses read via InternetReadFile loop, status banner updated with token count: Claude: generating... 142 tokens

New context menu layout:

Open Video File...
─────────────────────
Subtitle: Off
  Track 1 — filename.srt
  Track 2 — (empty)
  Track 3 — (empty)
─────────────────────
Generate Captions (Whisper)
Translate to English (Whisper)
─────────────────────
Generate Captions (LLM) ▶
  Claude (Anthropic)
  OpenAI
  Gemini
  DeepSeek
  Grok
─────────────────────
Clear Thumbnail Cache
─────────────────────
Check for Updates...

🔔 Auto-Updater — GitHub Release Check

On every launch, the player silently checks https://raw.githubusercontent.com/BlackBlazent/blackvideo-mini-player/main/latest.json using WinINet. If the version field is newer than the compiled-in version string, a notification badge appears on the menu button.

latest.json format (add to repo root):

{
  "version": "2.4.0",
  "release_url": "https://github.com/BlackBlazent/blackvideo-mini-player/releases/tag/...
Read more

BlackVideo Mini Player v2.3.0 — Offline Captions (Whisper), Subtitle Rendering & Track Fix

Choose a tag to compare

@LoneStamp LoneStamp released this 07 Mar 02:59

✅ What's in This Release

v2.3.0

🎙️ Offline Caption Generation — Whisper.cpp Integration

Generate subtitles for any video without an internet connection or API key.

  • Right-click → Generate Captions (Whisper) — transcribes the video's spoken audio and writes a .srt file beside the video. The result is auto-loaded into Track 1 when complete.
  • Right-click → Translate to English (Whisper) — same pipeline with Whisper's --translate flag, which outputs English subtitles regardless of the spoken language. Useful for non-English films with no existing subtitle file.
  • Player stays responsive while Whisper runs — the job is launched as a detached background process via WinExec. The player polls for the output .srt file each frame and loads it the moment it appears.
  • Status banner in the control bar — shows Whisper: generating captions... or Whisper: translating... while the job is running, and clears automatically when done.
  • Zero build-time dependencywhisper_bridge.adb shells out to whisper-cli.exe rather than linking against whisper.h. No whisper headers or import libraries needed in lib/.
  • Hot-swappable models — place ggml-base.bin, ggml-small.bin, ggml-medium.bin, or ggml-large.bin in build\models\ and the next job picks it up automatically. No rebuild required.
  • Auto-discoverywhisper-cli.exe and the model are resolved in order: BLACKVIDEO_WHISPER_PATH / BLACKVIDEO_WHISPER_MODEL env vars → build\ beside the exe → system PATH. No config file needed in production.

Translation language: Currently translates to English only. Whisper.cpp's --translate flag always targets English as the fixed output language. Support for translating to other target languages (Spanish, Japanese, Filipino, etc.) is planned for a future version.


📝 Real Subtitle Text Rendering

Loaded .srt files now display on-screen during playback — not just track labels in the menu.

  • srt_parser.adb — new module that parses .srt files at load time (handles UTF-8 BOM, Windows CRLF, multi-line cues, up to 4 096 cues). Stores all cues in memory for O(n) per-frame lookup.
  • Cue text pushed every frameplayer.adb calls SRT_Parser.Current_Text(pos) each frame and pushes the result to the C overlay via bv_ui_set_sub_text.
  • Subtitle overlay in ui_overlay.c — draws the current cue horizontally centred just above the control bar, with a semi-transparent shadow box for readability against any background colour.
  • Works with any externally loaded .srt file and with Whisper-generated .srt files (auto-loaded from Track 1).

🐛 Subtitle Track State Bug Fixed

The subtitle track was silently resetting to Off immediately after a track was selected or a file was loaded.

  • Root cause — when Open_Dialog (the PowerShell file picker) blocks via popen, SDL queues SDL_MOUSEBUTTONDOWN events in the background. When the dialog closes and the event pump resumes, those queued events fire Handle_Button_Down again. If the mouse had drifted over the Subtitle: Off row, CTX_SUB_NONE was triggered automatically, setting Sub_Active = -1 right after the user's selection.
  • FixCtx_Close_Tick records the SDL tick whenever the context menu closes. Any SDL_MOUSEBUTTONDOWN arriving within 250 ms of that moment is discarded. This absorbs all queued dialog-close events without affecting normal clicks.
  • Context menu label fixed — the Subtitle: Off label in the context menu now updates to Subtitle: Track N (in red) when a track is active, so the current state is always visible without opening anything else.

⚙️ .env File — Dev / Production Path Override System

  • main.adb loads .env at startup using SetEnvironmentVariableA for each KEY=VALUE line found in the file beside the exe. Blank lines and # comments are skipped.
  • Production mode — no .env file needed in a release package. Whisper paths resolve automatically from build\. The player silently skips the load step if the file is absent.
  • Dev mode — rename .env.example to .env and set BLACKVIDEO_WHISPER_PATH and BLACKVIDEO_WHISPER_MODEL to override the search paths during development (e.g. pointing to a whisper-bin-x64 folder outside the project).
  • Never commit .env — it is listed in .gitignore and contains local machine paths only.

🏗️ Build System

  • build.bat checks for Whisper assets (non-fatal) — prints [OK] if whisper-cli.exe and ggml-base.bin are found in build\, otherwise prints a setup reminder. Build continues regardless.
  • build.bat generates .env.example if none exists — creates a ready-to-rename template with commented-out BLACKVIDEO_WHISPER_PATH and BLACKVIDEO_WHISPER_MODEL lines.
  • build.bat copies Whisper DLLs (whisper.dll, ggml.dll, ggml-base.dll, ggml-cpu.dll) from lib\ to build\ alongside the other runtime DLLs, if present.

🎮 Controls

Keyboard

Key Action
SPACE Play / Pause
/ Seek −5 / +5 seconds
/ Volume +10 / −10
M Mute / Unmute
L Loop toggle
F Fullscreen toggle
ESC or Q Quit

UI Bar

Control Action
Seek bar Click or drag to jump/scrub
⏮ Prev Seek to start
⏯ Play/Pause Toggle playback
⏭ Next Jump near end
🔁 Loop Toggle loop
🔊 Volume Mute/unmute
1.0x Speed Cycle 0.5× / 1× / 1.5× / 2×
⛶ Fullscreen Toggle fullscreen
⋮ Menu Context menu

Context Menu (Right-click)

Item Action
Open Video File... Swap video (restart with new file)
Subtitle: Off / Track N Toggle subtitles off, shows active track name
Track 1 / 2 / 3 Activate loaded track, or pick a file for empty slots
Generate Captions (Whisper) Transcribe audio → .srt (auto-loaded)
Translate to English (Whisper) Transcribe + translate to English → .srt (auto-loaded)

📦 Requirements (Windows)

  • Windows 10 / 11 x64
  • ffmpeg.exe on PATH or in build\ (for Whisper audio extraction)
  • Runtime DLLs in build\:
DLL Source
SDL2.dll SDL2 release page (MinGW)
SDL2_ttf.dll SDL2_ttf-devel-2.0.12-VC
libfreetype-6.dll Bundled with SDL2_ttf-devel-2.0.12-VC
zlib1.dll Bundled with SDL2_ttf-devel-2.0.12-VC
avcodec-62.dll FFmpeg shared build
avdevice-62.dll FFmpeg shared build
avfilter-11.dll FFmpeg shared build
avformat-62.dll FFmpeg shared build
avutil-60.dll FFmpeg shared build
swresample-6.dll FFmpeg shared build
swscale-9.dll FFmpeg shared build

Optional — Whisper DLLs (only needed for caption generation):

DLL Source
whisper.dll whisper-bin-x64.zip
ggml.dll whisper-bin-x64.zip
ggml-base.dll whisper-bin-x64.zip
ggml-cpu.dll whisper-bin-x64.zip
  • GNAT Community 2021 (build-time only)

🚀 Usage

build\blackvideo-player.exe "C:\path\to\video.mp4"

Generate captions after opening:

  1. Right-click anywhere on the video
  2. Click Generate Captions (Whisper) or Translate to English (Whisper)
  3. Wait for the status banner to clear — subtitle is auto-loaded into Track 1

📁 New Files in This Release

File Purpose
src/srt_parser.ads/.adb SRT subtitle parser (BOM, CRLF, multi-line, up to 4 096 cues)
src/whisper_bridge.ads/.adb Whisper.cpp offline caption bridge (no whisper.h at build time)
.env.example Template for dev path overrides — rename to .env
WHISPER_SETUP.md Full Whisper setup guide, model upgrade table, troubleshooting

Built with Ada 2012 · FFmpeg 8.x · SDL2 · SDL2_ttf · whisper.cpp · GNAT Community 2021

Full Changelog: v2.2.0...v2.3.0

BlackVideo Mini Player v2.2.0 — Playback Controls Fix & Audio Stability

Choose a tag to compare

@LoneStamp LoneStamp released this 05 Mar 11:39

✅ What's in This Release

🖱️ Fully Working Playback Controls

Screenshot

All on-screen buttons now respond correctly to mouse clicks.

  • Fixed mouse button detectionSDL_MouseButtonEvent.button was being read from byte offset 13 instead of the correct offset 16 in the SDL2 struct layout (type@0 · timestamp@4 · windowID@8 · which@12 · button@16). Every click was registering as button 0 (nothing), making play/pause, loop, mute, speed, fullscreen, and seek-bar clicks all completely unresponsive. One offset fix restores all controls.
  • Fixed right-click context menu — right-click (button = 3) was never detected for the same reason. The context menu (Open File, Subtitle Track selection) now opens correctly on right-click anywhere on the video.
  • Seek bar and progress scrubber remain fully functional — their x/y coordinate offsets were already correct.

🔊 Audio Stability Fix for Long Videos

Audio no longer becomes scattered, mixed, or distorted after ~50 minutes of playback.

  • Fixed ring buffer race conditionPush_Audio (main thread) and Fill_Audio (SDL audio thread) both accessed Ring_Avail, Ring_Read, and Ring_Write with no synchronisation. Torn reads accumulate silently and eventually produce audible corruption after extended runtime. Fixed by wrapping all Push_Audio ring writes in SDL_LockAudioDevice / SDL_UnlockAudioDevice.
  • Ring buffer increased from 256 KB → 2 MB (~12 seconds at 44100 Hz stereo S16). H.264 decode bursts during complex scenes can produce audio faster than real-time for short periods; the larger buffer absorbs these without overflow.
  • Overflow strategy changed — drop incoming, not buffered — the old code dropped the oldest buffered audio when the ring filled, causing audible clicks in audio that was about to play. The new code silently truncates the incoming burst instead, which is inaudible.
  • Audio ring flushed on every seekAudio.Flush is now called after all seek paths (scrubber drag, click seek bar, Prev/Next buttons, keyboard ←/→, loop restart). Previously, stale pre-seek audio would bleed through for a fraction of a second after jumping to a new position.
  • Fixed SDL audio device sample rateSDL_AUDIO_ALLOW_FREQUENCY_CHANGE was previously passed, allowing SDL to silently open the device at a different sample rate and causing subtle pitch drift or sync issues over long files. The device is now opened with Allowed_Changes = 0 (exact 44100 Hz, S16 stereo required).
  • Added SDL_LockAudioDevice / SDL_UnlockAudioDevice bindings to sdl-audio.ads.
  • Added Audio.Flush procedure to audio.ads / audio.adb.

🏗️ Build System Fixes

  • ui_overlay.c moved from src/csrc/ — GPRbuild was scanning src/ and compiling the C file as Ada, producing a duplicate ui_overlay.o that caused multiple definition linker errors for every bv_ui_* symbol. The csrc/ directory is outside GPRbuild's source scan.
  • C overlay now compiles to ui_overlay_c.o (not ui_overlay.o) to avoid collision with the Ada-compiled ui_overlay.o that GPRbuild produces from ui_overlay.adb.
  • GPR linker uses -l:libSDL2_ttf.dll.a (explicit import library filename) instead of -lSDL2_ttf to guarantee the DLL import path is used rather than any static archive.
  • Added -lgdi32, -lusp10, -lrpcrt4 to Windows linker flags, required by HarfBuzz Uniscribe inside SDL2_ttf.
  • Fixed SDL2_ttf / SDL2 link orderlibSDL2_ttf must precede libSDL2 so the linker resolves SDL2_ttf's SDL2 dependencies correctly.
  • build.bat uses %ROOT% (absolute quoted path via %CD%) for all -I and -o flags, fixing silent gcc failures when the project path contains spaces (e.g. Vilma E. Agripo).
  • build.bat deletes stale build\obj\ui_overlay.o before building to prevent leftover objects from prior broken builds causing duplicate symbol errors.

🎮 Controls

Keyboard

Key Action
SPACE Play / Pause
/ Seek −5 / +5 seconds
/ Volume +10 / −10
M Mute / Unmute
L Loop toggle
F Fullscreen toggle
ESC or Q Quit

UI Bar

Control Action
Seek bar Click or drag to jump/scrub
⏮ Prev Seek to start
⏯ Play/Pause Toggle playback
⏭ Next Jump near end
🔁 Loop Toggle loop
🔊 Volume Mute/unmute
1.0x Speed Cycle 0.5× / 1× / 1.5× / 2× (display only)
⛶ Fullscreen Toggle fullscreen
⋮ Menu Context menu — Open File, Subtitle tracks

Right-click anywhere on the video to open the context menu directly.


📦 Requirements (Windows)

  • Windows 10 / 11 x64
  • Runtime DLLs in build\:
DLL Source
SDL2.dll SDL2 release page (MinGW)
SDL2_ttf.dll SDL2_ttf-devel-2.0.12-VC
libfreetype-6.dll Bundled with SDL2_ttf-devel-2.0.12-VC
zlib1.dll Bundled with SDL2_ttf-devel-2.0.12-VC
avcodec-62.dll FFmpeg shared build
avdevice-62.dll FFmpeg shared build
avfilter-11.dll FFmpeg shared build
avformat-62.dll FFmpeg shared build
avutil-60.dll FFmpeg shared build
swresample-6.dll FFmpeg shared build
swscale-9.dll FFmpeg shared build
  • GNAT Community 2021 (build-time only)

Important: Use SDL2_ttf-devel-2.0.12-VC for the SDL2_ttf DLL. Include libfreetype-6.dll and zlib1.dll from that package alongside SDL2_ttf.dll in build\.


🚀 Usage

blackvideo-player.exe "C:\path\to\video.mp4"

Built with Ada 2012 · FFmpeg 8.x · SDL2 · SDL2_ttf · GNAT Community 2021

Full Changelog: 1.1.0...v2.2.0

BlackVideo Mini Player v1.1.0 — Audio, Stability & Icon

Choose a tag to compare

@LoneStamp LoneStamp released this 01 Mar 06:30
Screenshot

✅ What's in This Release

🔊 Working Audio Playback

Audio now plays correctly for all common video formats. Previous builds were silent or produced scraping and choppy distortion every ~2 seconds.

  • Fixed planar audio format support (AV_SAMPLE_FMT_FLTP / fmt=8) — AAC and MP3 audio tracks in MP4 files now decode cleanly. The old code only passed data[0] (left channel plane) to swr_convert; all planes are now forwarded correctly via bv_swr_convert_frame() in C, which passes frame->data directly to libswresample.
  • Fixed SDL_MixAudioFormat self-mix bug — the callback was passing the output stream as both source and destination, doubling amplitude on every audio callback and causing audible clipping and distortion. Fixed by copying ring buffer data into a temporary buffer first, then mixing Tmp → Stream.
  • Resampler supports any input rate/format — 48000 Hz source files (Blu-ray rips, etc.) now convert cleanly to S16 stereo 44100 Hz for SDL2 output.
  • Ring buffer doubled to 256 KiB (~1.5 s capacity) to absorb decode bursts without dropping samples.
  • SDL audio buffer lowered to 2048 samples for lower output latency.

🎬 Correct Video Playback Speed

Video no longer runs slower than its source FPS.

  • Main loop now measures actual elapsed time per frame using SDL_GetTicks and sleeps only the remaining budget. Previously SDL_Delay(frame_ms) ran after decoding, meaning decode + scale + upload time stacked on top of the frame interval.
  • 60 fps video now plays at 60 fps; 24 fps at 24 fps.

💾 Fixed Heap Exhaustion Crash

The player no longer crashes with STORAGE_ERROR: heap exhausted after a few seconds.

  • Root cause: every decoded video frame allocated a fresh ~6.5 MB Ada heap buffer for RGB pixel data and leaked it. At 60 fps this consumed ~390 MB/sec of heap with no reclaim.
  • Fixed by pre-allocating a single RGB_Buffer at open time and reusing it every frame. Zero heap allocation per frame.

🪲 Fixed Wrong Stream Routing (NAL Unit Spam)

Hundreds of [h264] Invalid NAL unit size errors per second are gone.

  • Root cause: Ada's hardcoded AVPacket.stream_index offset (36) was wrong for FFmpeg 8.x. Audio packets were misidentified as video and sent to the H.264 decoder, which tried to parse AAC audio as NAL units.
  • Fixed with bv_packet_stream_index() — a C helper that reads stream_index via the real FFmpeg header so the offset is always correct regardless of FFmpeg version.

🔗 GNAT 2021 Windows Archive Linker Fix

Eliminated the persistent archive has no index; run ranlib build failure.

  • Root cause: GPRbuild recreates the mixed Ada+C archive during bind+link, overwriting any manual ranlib fix.
  • Fixed by removing C from the GPR project entirely. ffmpeg_helpers.c is compiled by build.bat using gcc directly and passed as an explicit .o to the linker — no archive, no ranlib.

🖼️ Custom Application Icon

The executable now shows the BlackVideo icon in Explorer, the taskbar, and the title bar.

  • resources/blackvideo.o (compiled from blackvideo.rc via windres) is linked in the GPR Linker package under the windows case.
  • Windows-only — does not affect Linux or macOS builds.

🛠️ Build Script Hardening (build.bat)

  • Detects and deletes stale .adb body files that caused spec does not allow a body errors.
  • Compiles ffmpeg_helpers.c separately with gcc before invoking gprbuild.
  • Validates FFmpeg headers and .a import libraries before building, with clear errors on missing files.
  • Copies runtime DLLs to build\ on success.

🎮 Keyboard Controls

Key Action
SPACE Play / Pause
/ Seek −5 / +5 seconds
/ Volume +10 / −10
M Mute / Unmute
F Fullscreen toggle
ESC or Q Quit

📦 Requirements (Windows)

  • Windows 10 / 11 x64
  • FFmpeg 8.x shared DLLs in build\: avcodec-62.dll, avformat-62.dll, avutil-60.dll, swscale-9.dll, swresample-6.dll
  • SDL2.dll in build\
  • GNAT Community 2021 (build-time only)

🚀 Usage

blackvideo-player.exe "C:\path\to\video.mp4"
blackvideo-player.exe --help

Download

Installer

Download blackvideo-mini-player

Built with Ada 2012 · FFmpeg 8.x · SDL2 · GNAT Community 2021

For BlackVideo mini player support

Full Changelog: https://github.com/BlackBlazent/blackvideo-mini-player/commits/1.1.0