Skip to content

KMediaViewer ​

图片 / 音频 / 视频统一展示组件:稳定占位、加载/解码/超时/重试、深色 16:9 播放器控制栏、动态水印、诚实的下载交互开关。源码卡片、预览与阅读页应共用本组件,保证三种上下文行为一致。

示例 ​

图片默认居中 · 自动 50%
正在解析媒体地址…

ready 事件尺寸:—

视频播放器Plyr 按需加载 · 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 ​

NameTypeDefaultDescription
kind'image' | 'audio' | 'video'—媒体类型(必填)
sourcestring—稳定媒体引用(URL 或调用方令牌),经 resolveSource 解析后使用
alt / titlestring''图片替代文本 / 悬停标题;alt 也作为默认下载文件名
posterstring''视频封面
intrinsicWidth / intrinsicHeightnumber—已知尺寸元数据,用于占位期 aspect-ratio 预留高度
align'left' | 'center' | 'right'图片 center,音视频 left布局对齐
widthnumber | '50%' | 'auto' …'auto'像素数、百分比字符串或 auto(图片=容器 50%,音视频=100%),均不超过容器
heightnumber | '50%' | 'auto' …'auto'显式设置后固定组件高度,媒体仅在容器内部按 contain 适配,不再用固有比例改变组件尺寸
timeoutMsnumber10000解析/加载超时毫秒数
allowDownloadbooleantrue下载交互开关,见下方边界说明
watermarkMediaWatermark | nullnull水印配置,默认关闭
resolveSource(source, signal) => Promise<ResolvedMediaSource | null> | ResolvedMediaSource | null—稳定引用 → 短期 URL 适配器;signal 在换源/卸载时中止
downloadHandler(source, resolvedUrl) => void | Promise<void>—自定义下载回调;未提供时默认请求 Blob,再通过同源临时 URL 下载,避免跨域地址忽略 download 后跳转
allowPlaybackRatebooleanfalse在播放器控制栏显示倍速按钮(音视频通用,位于原设置入口的位置):默认 1x 只显示图标,切换后显示对应倍速并点击弹出档位菜单;关闭时播放器没有任何倍速入口
playbackRatesnumber[][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 ​

NameParametersDescription
ready{ url, width?, height? }图片完成 load+decode、音视频到达可播放后触发;携带实际尺寸供调用方缓存为 intrinsic 元数据
errorreason: string解析/加载/超时失败
retry—用户点击重试
download-request—用户点击下载按钮(先于 downloadHandler)

Slots ​

NameDescription
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):

ts
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 }
}

自定义下载(例如走服务端鉴权下载端点):

html
<KMediaViewer
  kind="video"
  source="attachment://9c2d…"
  :resolve-source="resolve"
  :allow-download="true"
  :download-handler="(source) => openDownloadPage(source)"
/>