dsh-media-sideba
Manifest validRight-sidebar media tab for DeepSeek Harness: inline playback for local audio/video — HTTP Range for native formats, ffmpeg transcoding for mkv/avi/flv/wmv.
dsh-media-sidebar
DSH 右侧栏的媒体播放器 tab:在侧栏里就地播放本地音频与视频文件。
为什么单独做一个
- 内置的 Document Preview 只注册了 Office / Markdown / 代码 / 图片 / PDF / HTML / 纯文本,没有媒体渲染器,媒体文件会落到"不支持预览"。
- 已装的 dsh-knit 覆盖了图片与视频(
mp4 m4v webm mov ogv),但没有音频,且只在工作区根目录内解析路径。 workspaceFilesRemote 读字节有大小上限,视频动辄几百 MB,不适合。
所以本插件补的是:音频,以及工作区外的文件。
组件
| 半边 | 文件 | 作用 |
|---|---|---|
| 宿主 | src/host/index.js | GET /dsh-media/api/raw?path=<绝对或相对路径>&sessionId=<会话 id>,只读、只回环、扩展名白名单、HTTP Range(206) |
| 客户端 | src/client/client.js | 右侧栏两个 tab 类型 + 播放器 UI |
客户端无需构建:它由 window.__ModuleLoader__ 直接装载(与 dsh-knit 同一形态,用 React.createElement 而非 JSX)。
两个 tab 类型
| id | kind | 认什么 | 用途 |
|---|---|---|---|
dsh-media-sidebar:player | media | 无 patterns(页类型) | guide 入口 + 手动输入路径 |
dsh-media-sidebar:file | media-file | patterns: ['dsh-resource://file/**'] + canOpen | 聊天里点文件引用直接起播 |
地址类型的契约来自官方 bundle(非推测):
- 注册字段
{ id, kind, patterns, priority, canOpen, title }——ui-deliverables的changesReviewDefinition、ui-sidebar-documentpreview的textDefinition - 正文 props 为
{ useTabInfo, useResource, t },地址读作useTabInfo().tab.navigation.address——ui-plan的PlanPreview - 顶层导出形状
exports.inject = ['slots','locale']—— dsh-knit
内置文件预览是 priority: 'fallback' 认同一批地址且 canOpen 更宽,我们档位更高、canOpen 只认媒体扩展名 ——
所以只有媒体文件落到这里,其它文件仍走内置预览。
路径解析
- 绝对路径:直接用。
- 相对路径:必须带
sessionId,相对该会话的工作区根(session.header.cwd)解析; 没有会话就返回400 session-required-for-relative,绝不落到宿主进程的 CWD。
支持的类型
原生(浏览器能直接解码,按原文件 + 支持 Range 服务):
- 音频:
mp3 wav m4a aac flac oga opus weba - 视频:
mp4 m4v webm mov ogv
转码(浏览器普遍解不了,用 ffmpeg 实时转 H.264/AAC 流式送出):
- 视频:
mkv avi flv wmv - 音频:
wma
想加格式只需改 resolveKind 旁边那张表一行(决策逻辑只有一处)。
转码(ffmpeg)
- 发现:
DSH_MEDIA_SIDEBAR_FFMPEG_PATH优先,否则用 PATH 上的ffmpeg。 - 能力探测:启动时跑一次
ffmpeg -encoders,只认libx264。拿不到就不提供转码、 明确返回415 transcode-unavailable(带reason),而不是给一个必然失败的流。原生格式不受影响。 - 怎么转:
libx264 -preset veryfast -crf 23 -pix_fmt yuv420p+aac 160k, 输出-movflags frag_keyframe+empty_moov+default_base_moof -f mp4 pipe:1。-map 0:v:0? -map 0:a:0?的?让纯音频源也能走同一条命令。 - 令牌响应里多了
mode:native或transcode,客户端据此显示提示。
⚠️ 转码模式不支持拖动进度
实时转码产出的是无索引的分片流:浏览器能播,但拿不到时长,因此通常不能 seek。
响应会如实声明 accept-ranges: none,客户端也会显示一行说明。
要做到可拖动,只能改成"先转码落盘缓存、再按 Range 服务已完成文件"——那是另一种实现 (多一个缓存目录 + 清理策略 + 首次播放的等待)。当前版本刻意不做,换的是"点开就能播、不落盘"。
资源保护(转码吃 CPU,必须封顶)
- 并发上限 2:超出返回
429 transcode-busy(带active)。 - 客户端断开即杀:切文件 / 关面板 / 拖动都会中断请求,此时立刻
kill掉 ffmpeg 子进程。 - 硬超时 30 分钟:到点杀掉,避免僵尸进程长期占 CPU。
- ffmpeg 的 stderr 只在退出码非 0 时记进宿主日志(截断 500 字符),不回给客户端。
安全边界
- 只接回环来源,非回环一律
403 loopback-only; - 来源围栏(
rejectReason):Sec-Fetch-Site: cross-site、跨源Origin、以及任何转发头 (forwarded/x-forwarded-for/x-forwarded-host/x-forwarded-proto/x-real-ip) 一律403 origin-rejected。 少了这条,任何用户访问的网页都能对本路由发请求,并用 200/415/404 的差异 探测本机媒体文件是否存在;而若把dsh web挂在 localhost 反代之后,remoteAddress全是回环,远端就能直读。 - 扩展名白名单,不在表里的一律
415 media-only—— 这条端点读不到非媒体文件; - HMAC 令牌:
/raw必须带?token=,令牌 =HMAC-SHA256(secret, 文件的 realpath), 用timingSafeEqual校验(格式先卡^[0-9a-f]{64}$,再定长比较)。 见下方「令牌」。 - 相对路径限定在会话根内:
..逃逸返回403 outside-session-root; - 只读(
GET/HEAD),无写入通道;cache-control: no-store+nosniff; - 字节用
stream.pipeline送出:源流错误不会变成未捕获异常(不会崩掉宿主进程), 客户端中断(拖动进度)也会销毁源流,不泄漏 fd。
令牌
- 签发:
GET /dsh-media/api/token?path=<...>[&sessionId=<...>]→{ ok, path, contentType, token }。 它同样受回环、围栏、白名单约束,只认GET;不回字节,只发令牌。 - 绑定:令牌绑的是
realpathSync之后的目标,所以同一个文件的绝对写法、相对写法(经会话)、 符号链接、大小写差异都得到同一个令牌;换了文件则必然不同。 客户端因此必须"先要令牌 → 用宿主回的path拼/raw地址"。 - 有效期:不过期(与
OrderG-X/dsh-video-preview一致)。播放器整段播放期间会不断发 分段请求,短过期会让长视频中途断掉。代价是"泄漏的 URL 长期有效"。 - 轮换:删掉 secret 文件(下次启动重新生成)即让所有既有令牌立刻失效。
- 密钥位置:
$DSH_HOME/cache/dsh-media-sidebar.secret(可用环境变量DSH_MEDIA_SIDEBAR_SECRET_FILE覆盖,测试用它)。写入时带mode: 0o600, 但Windows 上 chmod 是空操作 —— 实际保护来自用户 profile 的 ACL,这点不粉饰。 - fail closed:密钥准备失败就不注册任何路由,不会退化成"无签名端点"。
仍未消除的风险(已知并接受)
- 本地进程可读:能连
127.0.0.1的本机进程可以自己调/token拿令牌,再按绝对路径取文件。 它本来就能直接读同一文件,所以本端点不扩大它的能力。令牌真正挡住的是 **"URL 被记录 / 分享 / 出现在日志里"**这一类,以及围栏之外的第二道"你这个调用方有没有被授权读过这个文件"。 - 绝对路径按设计放行:因此"可读集合"是整台机器上的音频/视频文件,仅由扩展名白名单约束。 这是"工作区外的文件也要能播"的代价。需要的话可改成"只允许配置的目录前缀"。
- 无速率 / 并发上限;无
ETag/Last-Modified(每次拖动重读,无正确性问题); 令牌不过期(见上)。 - 客户端打开即自动播放(
autoPlay,符合"点链接直接开始播放"的预期); "最近打开"把绝对路径写进localStorage。
测试
node --test test/ # 或 npm test
不打网络:用假的 ctx / req / res 直接驱动 apply() 注册出来的 handler,
测的就是真正会运行的那份代码。当前 28 条用例,覆盖:
- 回环检查、来源围栏(跨站 / 跨源 / 转发头)
- 扩展名白名单、未知子路由、方法限制
- Range:
start-end/start-/-suffix/ 不可解析回落到 200 / HEAD - 相对路径的会话解析、
..逃逸 - 令牌:缺令牌 / 别的文件的令牌 / 五种非法格式 / token 路由的围栏与 415 /
绝对与相对写法归一到同一
path与同一token/ 换密钥后旧令牌失效 - 转码:原生格式
mode = native、转码不影响原生 Range、 mkv 判为mode = transcode(无 libx264 时必须是415 transcode-unavailable)、 以及真跑一次 ffmpeg,断言/raw回的是以ftyp开头的 MP4 流且accept-ranges: none
转码那两条用例需要 ffmpeg + libx264;环境不具备时自动跳过(并用
transcode-unavailable那条兜住)。
安装
从 GitHub 源码装(推荐;本包没有构建步骤,装完即用):
dsh plugin --profile web add github:guwenwu112233-dot/dsh-media-sideba
或从本地目录装:
dsh plugin --profile web add /path/to/dsh-media-sidebar
装完重启 DSH Desktop(宿主插件只在进程启动时装载,客户端 bundle 按 revision 不可变,热重载都不管用)。
可选的转码支持:装好 ffmpeg(需要 libx264)并确保它在 PATH 上,或用
DSH_MEDIA_SIDEBAR_FFMPEG_PATH 指到具体可执行文件。没有 ffmpeg 时原生格式照常工作。
用法
右侧栏开始页 → 媒体播放器 → 输入文件绝对路径 → 播放。最近打开会记住(localStorage)。
聊天里点媒体文件引用(dsh-resource://file/...)也会直接开这个播放器并起播;
右侧栏文件树里点音视频文件同理。
已知限制
- 链接点击的适用范围:本插件认领的是
dsh-resource://file/...这类应用内文件地址。 如果聊天里是一个普通http(s)链接,它归官方 Browser tab(受"链接偏好"控制),不会进本播放器。 - 聊天是否把某个文件渲染成可点击引用,取决于消息侧的引用形式,不由本插件决定;本插件只负责 "一旦产生媒体文件地址,就由我接走并播放"。
- 不做播放列表。
- 解码取决于浏览器,转码取决于 ffmpeg:原生格式(
flac/opus/mov等)在部分环境可能不被解码; 转码格式(mkv/avi/flv/wmv/wma)必须有 ffmpeg + libx264,且不支持拖动进度(见上方「转码」)。 - 改代码后的加载时机(重要):宿主半边只在进程启动时装载,改
src/host/必须 重启 DSH Desktop;而客户端半边可以在刷新页面时被重新下发。 所以"刷新了页面但没重启"会出现新客户端 + 旧宿主的错配 —— 典型症状是客户端报not-found-route(它在请求宿主还不认识的/dsh-media/api/token)。 改完src/就重启,别只刷新。
Comments
Loading…
Similar plugins
by OrderG-X
Inline video and audio playback inside DeepSeek Harness: a play_video tool plus a native player at the turn tail, served by a signed Range-capable streaming route.
★ 0
MIT
JavaScript
Oct 11, 2026
dsh plugin --profile web add dsh-video-previewby tsonglew
Audio/video preview viewer for dsh-better-sidebar: native playback with Range-seeking streaming route
★ 3
MIT
JavaScript
Aug 14, 2026
dsh plugin --profile web add dsh-media-previewby AKS1st
Media player for the DSH dock: plays audio (music player) and video (fullscreen) files, streamed over HTTP Range.
★ 0
MIT
TypeScript
Sep 11, 2026
dsh plugin --profile web add dock-mediaby yuehancn
DeepSeek Harness plugin
★ 0
MIT
JavaScript
Oct 2, 2026
dsh plugin --profile web add dsh-tool-mediaby seeingrain
All-in-one inline media previewer for img/video/audio fences with streaming playback, ffmpeg transcoding fallback, lightbox, and trust-fenced LAN/mobile access.
★ 0
MIT
JavaScript
Sep 12, 2026
dsh plugin --profile web add dsh-media-inline-previewby xiaokaizhou
Renders local audio/video paths as inline, playable previews in DSH chat: replaces paths with <audio>/<video> players backed by same-origin /api/media-preview/* route, with explicit error statuses.
★ 0
MIT
JavaScript
Sep 21, 2026
dsh plugin --profile web add media-preview