dsh-video-player


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 alink: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 ishttp://host.docker.internal:9999with
/scene/{no}/streamper 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/streamsuffix). 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 thedsh-video-playersettings
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 pollsGET /api/dsh-video-player/config(~2.5s)
and pushes title-bar changes back withPOST, so the floating window and the
settings store stay in sync.
Bugs we hit and fixed (the lessons)
- Host
export defaultdropsinject. DSH's bundle loader unwraps a
module'sdefaultexport to the bare function, silently discarding
inject/name— services then throwcannot get property "webServer" without injectat 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
declaredinject: ["@deepseek-ai/dsh-client-runtime"]without requiring it,
and sat forever inpending (waiting for service: …). Fix: keep the client
self-contained withinject: []. - 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-playersettings 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.