dsh-video-player
Video player mid-Stash
Video player on Stash
A floating, draggable, resizable video player for DeepSeek Harness (DSH).
It mounts a small window over the web UI that plays scene-per-MP4 clips from a
Stash-style scene server — propping a video feed beside your chat and paging
through clips while you work. The window moves and resizes in real time, like a
real PiP video. Stash is the star; other sources are best-effort.
Install
npm install -g pnpm@11 # the container image ships no pnpm
dsh plugin --profile web add /path/to/dsh-video-player
# restart the web profile (bundle changes apply at boot only):
sh ~/.dsh/dsh-guard/restart-web.sh
Linked installs need an import bridge. dsh plugin add <dir> installs the
plugin as a link: symlink, and Node resolves the host module's imports from
its realpath (/path/to/dsh-video-player), which sits outside the profile's
node_modules. Before installing from a local dir, give it a bridge:
mkdir -p /path/to/dsh-video-player/node_modules/@deepseek-ai
ln -s ~/.dsh/profiles/node_modules/@deepseek-ai/dsh-settings /path/to/dsh-video-player/node_modules/@deepseek-ai/dsh-settings
ln -s ~/.dsh/profiles/web/node_modules/schemastery /path/to/dsh-video-player/node_modules/schemastery
Features
- Floating, resizable window — drag the title bar to move it, drag the
bottom-right corner to resize live (min 200×140). Native
<video> player with
controls (play/pause, scrub, fullscreen, volume). Inputs and buttons in the
title bar are excluded from the drag handler, so they stay clickable/typable.
- Stash scene navigation (the star) —
◀ / ▶ step scenes, a number
field + Go / Enter jumps anywhere, and a status line shows the exact stream
URL. The default server is http://host.docker.internal:9999 with
/scene/{no}/stream per clip; it supports Range requests (verified
206 Partial Content), so seeking works.
- Channel dropdown (custom widget) — Stash / Custom link / YouTube / Twitch
/ Jellyfin. Built as buttons, not a native
<select> (which was unreliable in
DSH's dark UI). Picking a channel switches the source immediately.
- Link field with auto-detect — paste a YouTube / Twitch / Jellyfin /
direct-file link and it auto-picks the channel and plays it (never appends a
/scene/N/stream suffix). Enter or the ▶ button plays; paste auto-plays.
- Gear panel (⚙) = window + Stash config only — width, height, opacity, and
the Stash base URL (scene server root). Source/channel config deliberately
does NOT live here.
- Persisted geometry — position, size, and opacity survive reloads
(localStorage).
- Coexists with the pet & themes — it's a separate fixed overlay alongside
the DSH pet and dream-skin wallpaper.
Architecture
- Host half (
lib/index.js) — registers the dsh-video-player settings
namespace (schema: base, channel, url, scene) and serves
GET/POST /api/dsh-video-player/config. The POST handler writes through the
DSH settings service, so the source config persists in the real settings
store (not localStorage).
- Client half (
lib/client.js) — self-contained (plain DOM, inject: [],
activates immediately). It polls GET /api/dsh-video-player/config (~2.5s)
and pushes title-bar changes back with POST, so the floating window and the
settings store stay in sync.
Bugs we hit and fixed (the lessons)
- Host
export default drops inject. DSH's bundle loader unwraps a
module's default export to the bare function, silently discarding
inject/name — services then throw cannot get property "webServer" without inject at boot and take the whole app down. Fix: use named
exports only (export function apply, export const inject, export const name), exactly like the dsh-pet host bundle.
- A client that declares an inject it never uses hangs. The client once
declared
inject: ["@deepseek-ai/dsh-client-runtime"] without requiring it,
and sat forever in pending (waiting for service: …). Fix: keep the client
self-contained with inject: [].
- Host imports don't resolve for
link: installs. See the bridge note
above.
Roadmap
- Layer control — put the window in front of chat (default), behind it /
tuck into the sidebar, or "minimize to sidebar".
- Transparency — window opacity (done: 0.3–1.0 in the gear panel) plus a
click-through toggle for when you want to watch but not interact.
- Alarm / "someone walked in" button — one click plays a sound (TBA).
- Scene thumbnail rail — thumbnails for the current scene ± a few, for
faster paging.
- Settings-menu card — the
dsh-video-player settings namespace is
registered; if the generic settings surface doesn't render it as its own
section, add a client settings card (like the pet's) so channel/link are
editable from Settings → dsh-video-player.
- Stash scene list — probe the server for valid scene numbers and page
within the real set instead of ±1.