Ghostfader — sync-locked stem player for audio post-production workflows
One video, up to six audio layers, all in lockstep. Toggle stems on and off independently while playback continues; sync stays within 30 ms of picture. Embedded into a WordPress page, optionally access-restricted, sent as a single URL.
Built for the way film and TV post actually works: the same cut needs to be reviewed by different people for different things, and everyone needs it in sync.
Sync-locked review from your browser. Most post-houses use a video-player and Excels and PDFs. Frame.io and alike treat audio as secondary. There’s no tool in the market that allows for this at any level. And now it’s becoming available for the freelance engineer.
With features such as:
– User Authorization controls
– Customizable player
– Multi-channel audio support
– Up to 6 stems of soundtrack
– time-stamped review with export
For who?
Sound editors send one review URL with picture-lock plus dialogue, music, and FX as switchable layers. Directors A/B between mix balances live; alternate versions and M&E passes are toggles, not new exports. No more “can you also send a no-music version?”.
Directors see picture and hear any combination of the audio. Solo the music to feel a scoring choice, mute it for dialogue clarity, pull just FX to evaluate the soundscape — no DAW, no extra render request. Review on the studio monitor, revisit at home from the same link.
Producers / Post-supervisors get one review URL per asset, with per-project access lists. Restrict views by user group, see privacy-compliant analytics on who’s reviewed what, and keep every asset inside your WordPress install — no third-party streaming service, no file downloads to track.
Executives / Post-house clearance / QC can compare original vs dubbed dialogue against picture, switch between language tracks while music and FX play unchanged, and verify lip-sync holds across versions. M&E + dialogue split, sub-vs-dub QC, version comparison, comment — all in one place.
Why “sync-locked”?
The playback engine treats sync as the contract: media won’t start until every stream is verified ready and aligned within frame tolerance, and a runtime correction loop keeps each stem in step during playback. There’s no overridec. The whole point is that what your reviewer hears is what you sent without the loss of audio fidelity.
Questions or interested in a demo? Contact us.
# Ghostfader — Development Log
Ghostfader is tool built for sync-mandatory client review of video and audio: one video, up to six audio stems (dialogue / music / FX / and so on), all locked together with sub-frame drift. Developed in-house and built for our own post-production review work.
This page tracks every release. Each entry is roughly what you'd see in a commit log: short, specific, and honest about what changed and why.
## Follow along
If you want to be notified when a new version ships:
**Get in touch** — (https://noordersound.com/contact) Happy to discuss licensing, custom integrations, or send a demo link.
---
## Releases
### v0.8.5 (2026-05-06) — Sync-is-holy hardening
Field testing of v0.8.4 surfaced three contract violations: stems loaded sequentially, the gate resolved prematurely after seeking into already-buffered ranges, and the "Play anyway" override silently undermined the sync guarantee. v0.8.5 rebuilds the gate around the browser's native readiness signal and removes the bypass entirely. Subsequent field testing of the rebuilt gate surfaced ten more edge cases in the seek path, retry flow, and prebuffer UX — all folded into this release.
**Fixed**
- **Stems now load in parallel.** `<audio>` elements emit `preload="auto"` from the initial HTML render. Browsers parallelize concurrent media loads up to the per-host connection limit. The player's `Preload` setting now controls video only.
- **Gate uses browser's native readiness signal.** Replaced the arbitrary "5 seconds buffered" threshold with `readyState >= HAVE_ENOUGH_DATA (4)` on every media element — the same value that fires `canplaythrough`. Plus a small defensive buffer floor (default 1.0s, configurable per player) on the slowest stream as insurance against `readyState` flapping. Releases ~1 s on broadband instead of always waiting 5 s.
- **Progress ring drives off readiness count.** The ring now reflects "how many streams are ready" rather than "how much buffer is loaded" — honest indicator.
- **Stall recovery routes through the gate.** The runtime sync correction's recovery path no longer resumes on a partial buffer; it routes through the full prebuffer gate, which guarantees the buffer has actually replenished.
**Added**
- **`window.online` / `window.offline` handlers.** When the network drops mid-playback the player pauses cleanly; when it returns the prebuffer gate re-arms before resuming. Self-healing across wifi drops, mobile handoffs, server hiccups.
- **Halt overlay with Retry.** When the gate times out at 60 s OR a media error fires (network failure, decode error, source unavailable, load aborted), the player overlays a halt card with title, detail, and a Retry button. Specific error code per HTML5 spec is named in the message.
- **60-second halt timeout** with bandwidth-honest message: "this player needs roughly 5 Mbps of stable bandwidth to stream video + N audio tracks in sync." No magic, no false promises.
- **Escalating UI:**
- 0–15 s: silent ring fill
- 15–30 s: "Loading — slower than expected"
- 30–60 s: "Connection issue — N s before timeout" with live countdown
- 60 s+: halt overlay + Retry
- **Per-player Buffer-floor field** in the admin UI. Default 1.0 s, configurable.
- **Per-stem sync diagnostics in Debug Info overlay.** A second row per stem shows drift Δ vs video (signed ms), current playback rate, and per-stem controller integrator value. Pure observability — no behavioral change.
- **Network requirements documentation** in the README.
**Removed**
- **"Play anyway" override.** The 15-second escape hatch from earlier versions was the wrong default for sync-mandatory client review. Sync is now mandatory; on persistent slow connections the player halts after 60 s with Retry. No bypass.
**Post-release hardening (field testing).** Ten edge cases surfaced during field testing of the rebuilt gate. All fold into the same v0.8.5 release.
- A redeclaration bug introduced by the diagnostics commit broke every player on the page (entire JS module failed to parse). Renamed the conflicting variable.
- Removed a "quick path" that skipped alignment verification when buffer was already full — stems that drifted during pause could resume out of sync.
- Audio leak through scrub drag: stems kept playing through the entire drag while video was loading the new position. The seek handler now pauses stems immediately.
- Gate could resolve before audio seeks had actually landed (currentTime returns the *target* during a seek, not the actual position). Gate now waits for `audio.seeking === false` on every stem.
- Stall-recovery fallback raced an in-flight gate; split the fallback so an active gate owns the resume decision.
- Play button click could bypass the running gate; now respects the disabled state.
- Halt-overlay Retry reset playback to 0:00 because `video.load()` resets `currentTime`. Now saves position before reload and restores it after metadata reparse.
- Prebuffer ring was effectively invisible due to a CSS filter that desaturated it against the play button. Filter softened.
- Prebuffer ring DOM wasn't created when buffer floor was 0; threshold relaxed.
- Timeline scrub chain-fired gates per micro-movement, locking the knob mid-drag. Scrub now commits on release; visual feedback during drag is preserved.
**Changed**
- **Default buffer floor bumped from 0.5 s → 1.0 s.** 0.5 s released the gate fast on broadband but left no headroom against momentary blips. 1.0 s still releases sub-second on fast connections and gives the readyState gate proper safety margin.
- **PI controller integrators reset on every play event.** Prior wind-up no longer carries across pause/resume. Each play session starts the controller from a clean state.
---
### v0.8.4 (2026-05-03) — Stabilization patch + privacy logging
Stabilization patch release coming out of the v0.8.3 audit. Two real Access Groups bugs fixed, privacy posture flipped to opt-in with a new pseudonymized logging schema, debug overlay trimmed, and several internal cleanups. No new player features.
**Access Groups**
- **Fix redirect loop**: a logged-in user not in the access group was redirected to the WP login page, which redirects them right back, infinite loop. Logged-in denials now return 403.
- **Fix slug-based shortcode bypass**: `[ghostfader slug="x"]` shortcodes bypassed the gate entirely (the regex only matched `id=N`). Slugs now resolve correctly and are gated identically.
- **Per-request access-check cache**: a page with multiple restricted players sharing one group used to query the same group's members N times. Now memoized per-request.
**Privacy and logging**
- **Logging is off by default on fresh installs.** Existing installs are unaffected.
- **Anonymous logging endpoint dropped.** Eliminates DoS surface on the logs table and most of the privacy concern around scraped nonces.
- **Privacy-compliant logging schema (Tier 3+ hybrid pseudonymization)**:
- Authenticated viewers logged by WordPress user ID directly (lawful basis: contract).
- Anonymous visitors logged by daily-rotating SHA-256 hash. After 24 h, prior hashes can no longer be linked to current sessions.
- A separate `network_hash` enables session-sharing detection without retaining device-level IP.
- Browser/OS reduced to coarse buckets (e.g. "Chrome 138 on Windows"). Minor/patch/build deliberately discarded.
- Raw IP and full user-agent are no longer stored in new rows.
- Daily salt rotation via cron.
- One-time admin notice explains the migration.
- Settings page gains a privacy-notice paragraph and a "Salt last rotated" timestamp.
**Debug overlay (hidden support tool)**
- Trimmed plugin enumeration, memory, CPU cores, and connection-speed measurement — weak fingerprinting vectors with near-zero support value.
**UX**
- **Help modal title**: renamed "Keyboard shortcuts" → "Help".
**Cleanup**
- Removed dead code declarations and a duplicate "Player saved" admin notice.
---
### v0.8.3 (2026-05-03) — Universal pre-roll buffer + sync gate
- **Universal pre-roll buffer + sync gate**: any time playback is about to start from a position — first play, resume from any pause length, or seek to any new position — the player verifies forward buffer on video AND every stem AND hard-aligns stems within 30 ms tolerance before media moves. A thin fill ring around the play button shows progress; no spinner, no text. Sync is enforced at every entry point.
- **Slow-connection override** (later removed in v0.8.5): after 15 s without reaching the threshold, a discreet "Slow connection — Play anyway" notice appeared.
- **Tooltip overflow fix**: edge controls now anchor their tooltips inside the player frame.
---
### v0.8.2 (2026-05-03) — Player duplicate + Access Groups UX
- **Player duplicate**: one-click duplicate from the player list — creates "Copy of [title]" with all settings copied.
- **Access Groups UX**: saving a group now redirects to the list view with a success or error notice; no longer strands the user inside the editor.
- **Group member selector**: replaced multi-select with a single-click checkbox list — no Ctrl/Cmd required.
- **Debug Info overlay**: hidden support tool showing stream stats, browser/OS, codec support, browser features, plugins, and live datetime. Triggered via `I` key or Shift+Ctrl+Click on the `?` button.
---
### v0.8.1 (2026-05-02) — Help modal footer + upgrade routine
- **Help modal footer**: plugin version displayed discretely.
- **Support link**: configurable URL shown alongside the version. Hidden when blank.
- **Upgrade routine**: version-aware migration on `plugins_loaded` fills missing option keys on existing installs; scaffolds a path for future schema changes.
---
### v0.8.0 (2026-03-27) — Access Groups
- **Access Groups**: create named groups, add WP users to each, and assign groups to a player. Only users in an assigned group can view the player page — unauthorized visitors are redirected to the WP login page. No group assigned = publicly accessible.
- New admin submenu: Ghostfader → Access Groups (list, create, edit, delete)
- Player editor gains an "Access" section with group checkboxes
- Page-level gate via `template_redirect` — entire page redirects, not just the shortcode
---
### v0.7.6 (2026-03-15) — Seek buffer gate
- **Seek buffer gate**: after seeking, all media must be buffered at the new position before playback resumes — prevents momentary desync.
- Folds in v0.7.2 hotfixes (accent color inline style, admin save redirect).
---
### v0.7.2 (2026-03-14) — Controls below video
- **Controls below video**: dock (transport + stems) moved from overlay to flow element beneath the video.
- Clean video frame — no controls covering the picture.
- **Fullscreen**: video fills available space, controls pinned at bottom, auto-hide after 3 s idle.
- Normal view: controls always visible (no auto-hide).
---
### v0.7.1 (2026-03-14) — Windowed player + chrome bar
- **Windowed player**: video sits in a padded frame (was seamless edge-to-edge).
- **Chrome bar**: top bar with brand, project title, and help button.
- **Help modal**: `?` button opens keyboard shortcuts overlay (Escape to close).
- Fullscreen mode hides chrome bar and removes padding for immersive viewing.
---
### v0.7.0 (2026-03-14) — Admin theming + per-player accent
- **Admin theming**: accent color picker (WP Color Picker) in Settings page.
- **Font selector**: choose from System UI, Inter, Roboto, Roboto Mono, DM Sans, Space Grotesk.
- **Per-player accent override**: set a custom accent color per player in the editor, or via shortcode `accent="#hex"`.
- CSS custom properties injected via `wp_add_inline_style`.
- Google Fonts auto-enqueued when a non-system font is selected.
- Play button now uses accent color instead of white.
---
### v0.6.0 (2026-03-14) — Unified dock + mobile responsive
- **Unified dock**: transport + stem controls merged into one bar, overlaid inside the video frame.
- Controls are now visible in fullscreen mode.
- **Auto-hide**: dock fades out after 3 s of inactivity during playback, reappears on mouse/touch.
- **Awaiting-play state**: controls start grayed out, come alive on first play.
- **Mobile responsive**: 44 px minimum touch targets, responsive stem layout, smaller brand mark.
- **Tooltips**: CSS-only tooltips on all controls (hidden on touch devices).
---
### v0.5.0 (2026-03-14) — Admin shortcode builder + WP Media Library
- CF7-style admin shortcode builder with custom-post-type-based player storage.
- WP Media Library integration for video/poster/stem file pickers.
- SVG branding (banner + icon) with top-left watermark in player.
- Tooltips on all interactive controls.
- Assets reorganized into `dist/` and `img/` subdirectories.
---
### v0.4.0 (2026-03-14) — Clean video frame + redesigned controls
- Redesigned player UI: video frame is now completely clean — no overlays or controls on picture.
- Stem controls moved below transport bar as horizontal lit/unlit toggle buttons with inline faders.
- All stems start ON by default to avoid confusion.
- Removed master volume fader (system volume handles overall level).
- Added SVG branding assets (icon + banner).
---
### v0.3.1 (2026-03-14) — All-or-nothing sync
- Enforced all-or-nothing sync: if any stem or the video buffers, everything pauses.
- Stall recovery hard-aligns all stems to video time before resuming.
- Subtle progress bar pulse during buffering (no text or explanations).
---
### v0.3.0 (2025-09-03) — Cinematic UI + per-stem sync controller
- Unified cinematic dark player UI with CSS custom properties for theming.
- Per-stem PI sync controller with adaptive gains based on content duration.
- Per-stem integrators replacing shared integrator (fixes cross-stem drift).
- `requestVideoFrameCallback` for frame-accurate sync (interval fallback).
- Ready gate: waits for all media `canplaythrough` before enabling controls.
- Crossfade ramp scheduler for smooth mute/unmute transitions.
- Dynamic stem count (1–6 stems, no longer hardcoded to 3).
- CF7-style admin panel: player list, editor with WP Media Library pickers, shortcode builder.
- Custom post type (`gf_player`) for storing player configurations.
---
### v0.2.0 (2025-09-03) — Logging + per-stem volume
- Expanded shortcode with attributes for autoplay, loop, preload, persist, and logging.
- Added per-stem mute/solo buttons and volume faders.
- Added master volume control and reset button.
- Introduced local persistence (per-player ID) using localStorage.
- Introduced lightweight logging/analytics: play, pause, seek, milestones, mute/solo events.
- Logging stored in custom DB table with CSV export.
- Added WordPress Admin menu with global settings, shortcode builder, and logs viewer.
- Improved keyboard accessibility: mute/solo via 1–9 keys, solo via Shift+click.
- Improved error handling: missing video displays clear message.
---
### v0.1.0 (2025-08-30) — First alpha
- First alpha release.
- Shortcode-based player for WordPress.
- Supports one MP4 video with optional poster and logo.
- Supports exactly three stems (Dialogue, Music, SFX).
- Stem toggles with Shift+click solo.
- Transport controls: play/pause, seek bar, elapsed/remaining time.
- Master volume slider.
- Fullscreen toggle.
- Keyboard shortcuts for play/pause (Space), seek, fullscreen (F), and solo via Shift+click.
- License: All Rights Reserved (c) Noordersound.