Bundle
dsh-video-player
Floating, draggable, resizable video scene player for DeepSeek Harness. Plays scene-per-MP4 clips from a Stash-style scene server, plus best-effort YouTube / Twitch / Jellyfin / custom links.
- Source
- IP050
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 22 hours ago
Readme
# 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
```sh
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:
>
> ```sh
> 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)
1. **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.
2. **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: []`.
3. **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.
Install
dsh plugin --profile web add github:IP050/dsh-video-player
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-video-player from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.