KMediaViewer
图片 / 音频 / 视频统一展示组件:稳定占位、加载/解码/超时/重试、深色 16:9 播放器控制栏、动态水印、诚实的下载交互开关。源码卡片、预览与阅读页应共用本组件,保证三种上下文行为一致。
示例
播放器选型验证(2026-09-09)
| 项 | 结论 |
|---|---|
| 版本 | plyr@3.8.4(锁定精确版本,见 package.json dependencies) |
| 许可证 | MIT |
| 样式 | plyr/dist/plyr.css(约 32.5 KB),通过 ?inline 内联进 plyrBundle 懒加载 chunk,随音视频分支一起到达,无需手动 <link> |
| 全屏 | fullscreen.container 支持指定选择器使容器(含水印层)在全屏中保持可见;fallback: true 提供容器内全屏降级 |
| PiP | 控件中可移除 pip 入口,同时给媒体元素设置 disablePictureInPicture,双保险屏蔽绕过水印层的画中画 |
| 打包 | 不引入第三方 Vue 包装器,Plyr 由本组件内部管理初始化与 destroy();图片分支不触发任何播放器下载 |
Props
| Name | Type | Default | Description |
|---|---|---|---|
| kind | 'image' | 'audio' | 'video' | — | 媒体类型(必填) |
| source | string | — | 稳定媒体引用(URL 或调用方令牌),经 resolveSource 解析后使用 |
| alt / title | string | '' | 图片替代文本 / 悬停标题;alt 也作为默认下载文件名 |
| poster | string | '' | 视频封面 |
| intrinsicWidth / intrinsicHeight | number | — | 已知尺寸元数据,用于占位期 aspect-ratio 预留高度 |
| align | 'left' | 'center' | 'right' | 图片 center,音视频 left | 布局对齐 |
| width | number | '50%' | 'auto' … | 'auto' | 像素数、百分比字符串或 auto(图片=容器 50%,音视频=100%),均不超过容器 |
| height | number | '50%' | 'auto' … | 'auto' | 显式设置后固定组件高度,媒体仅在容器内部按 contain 适配,不再用固有比例改变组件尺寸 |
| timeoutMs | number | 10000 | 解析/加载超时毫秒数 |
| allowDownload | boolean | true | 下载交互开关,见下方边界说明 |
| watermark | MediaWatermark | null | null | 水印配置,默认关闭 |
| resolveSource | (source, signal) => Promise<ResolvedMediaSource | null> | ResolvedMediaSource | null | — | 稳定引用 → 短期 URL 适配器;signal 在换源/卸载时中止 |
| downloadHandler | (source, resolvedUrl) => void | Promise<void> | — | 自定义下载回调;未提供时默认请求 Blob,再通过同源临时 URL 下载,避免跨域地址忽略 download 后跳转 |
| allowPlaybackRate | boolean | false | 在播放器控制栏显示倍速按钮(音视频通用,位于原设置入口的位置):默认 1x 只显示图标,切换后显示对应倍速并点击弹出档位菜单;关闭时播放器没有任何倍速入口 |
| playbackRates | number[] | [0.5, 0.75, 1, 1.25, 1.5, 2] | 倍速菜单档位,自动过滤非正数/非数值并升序去重 |
MediaWatermark:{ text, opacity?(0–1, 默认 0.15), fontSize?(8–96, 默认 14), color?, rotate?(-180–180, 默认 -22), gapX?(60–600, 默认 140), gapY?(40–400, 默认 90) }。文本仅作为编码 SVG 平铺渲染,绝不作为 HTML 注入。水印覆盖整个展示区(含音频卡片),pointer-events: none 不阻挡操作。
Emits
| Name | Parameters | Description |
|---|---|---|
| ready | { url, width?, height? } | 图片完成 load+decode、音视频到达可播放后触发;携带实际尺寸供调用方缓存为 intrinsic 元数据 |
| error | reason: string | 解析/加载/超时失败 |
| retry | — | 用户点击重试 |
| download-request | — | 用户点击下载按钮(先于 downloadHandler) |
Slots
| Name | Description |
|---|---|
| default | 覆盖在展示区右下角的工具区(稳定容器,支持 hover / focus-within),编辑器可将删除、布局工具放入 |
状态机
resolving → loading → ready | error。URL 解析成功不代表加载成功:图片完成 load 与 decode 后才挂载显示,此前只有占位;已知尺寸用 aspect-ratio 预留最终高度,未知尺寸使用固定初始框(图片 4:3、视频 16:9),获知实际尺寸时最多调整一次,不会先坍塌再回弹。切换源或卸载会取消旧任务,旧请求结果不能覆盖新状态。音视频等待 loadedmetadata / canplay,Plyr 与其样式仅在音视频分支动态加载。
下载开关的真实边界
allowDownload=false 只是交互限制:隐藏下载按钮、阻止组件区域内的右键下载菜单、设置 controlsList="nodownload"。它无法阻止 F12 开发者工具、Network 面板、缓存或脚本提取已传输到浏览器的媒体字节;水印同样是覆盖层,不是防篡改或烧录水印。该布尔值不是服务端授权凭据——真正的下载权限必须由服务端策略控制。若需要更强的保护,需另行设计加密分发 / DRM 方案,且仍不能保证绝对无法复制。
Basic Usage
Prototype-aligned video controls
Video instances use a dark 16:9 stage with a bottom control bar, volume and fullscreen, and optional download. Set watermark.mode to single for one moving mark (the {time} token is refreshed every second), or leave it as grid for a tiled watermark. Set allowPlaybackRate (default false) to show a dedicated rate button in the control bar where the settings entry used to live — icon-only at the default 1x, the active rate (e.g. 1.5x) once changed; playbackRates customizes its menu options. Audio cards get the same button.
从稳定引用解析短期 URL 的适配器(组件不依赖任何业务 API / 令牌 / SDK):
const resolve = async (source: string, signal: AbortSignal) => {
if (!source.startsWith('attachment://')) return { url: source }
const uuid = source.slice('attachment://'.length)
const response = await fetch(`/api/attachments/${encodeURIComponent(uuid)}/media-preview-url`, { signal })
if (!response.ok) return null
const { url, width, height } = (await response.json()).data
return { url, width, height }
}自定义下载(例如走服务端鉴权下载端点):
<KMediaViewer
kind="video"
source="attachment://9c2d…"
:resolve-source="resolve"
:allow-download="true"
:download-handler="(source) => openDownloadPage(source)"
/>