更新记录

1.0.1(2026-11-07) 下载此版本

  • 修正插件市场描述:按市场 100 汉字上限精简,并把「小程序与 H5 明确降级」改为准确表述——微信小程序在基础库提供 UDP/TCP Socket 时条件支持真实链路,其余小程序与 H5 降级。
  • 插件标签按市场 5 个上限收敛为:投屏、DLNA、UPnP、SSDP、uts。
  • 源码契约测试新增两项市场限制守护(描述 ≤100 字符、标签 ≤5 个),避免下次发版被服务端拒绝。

1.0.0(2026-11-07) 下载此版本

首个公开版本:局域网媒体投屏 UTS 插件(DLNA / UPnP)。

  • 设备发现:SSDP M-SEARCH 组播 + 单播响应解析;组播不可用时可用 describeRenderer() 直连设备描述,不伪造设备列表。
  • 投送与控制:SetAVTransportURI / Play / Pause / Stop / Seek / SetVolume / SetMute,状态只在接收端回报或事件到达时更新,不把「命令已被接受」当成「播放成功」。
  • 内置控制面板 up-castplay-panel:设备名册(已连接高亮)、媒体信息与封面、播放/暂停、可拖动进度条、音量条、清晰度 / 字幕 / 倍速、节目单选集与上下集、交接续播、可折叠诊断行;面板只读 session 真实状态。
  • 进阶能力:ConnectionManager 能力协商、节目单选集、清晰度、字幕文本轨(DIDL-Lite 第二个 res)、倍速、交接续播凭证、本地文件与字幕的局域网 HTTP 发布(支持 Range)、系统路由选择器、GENA 事件订阅。
  • 多会话:显式 session 取代全局单例,可同时管理多个接收端。
  • 平台:App(Android / iOS / HarmonyOS)原生 UDP 组播 + HTTP 传输;微信小程序在基础库提供 UDP/TCP Socket 时条件支持;H5 与其他小程序明确降级(异步操作返回 30002),同步能力查询、节目单状态与媒体预检仍可用。
  • 协议与会话状态机收敛在平台无关核心,原生层只负责传输;错误码 30001–30016。
  • 示例工程内置可复跑的验证资产:由真实 wire.uts 生成的报文夹具、模拟 UPnP 接收端(真实视频流与播放页)、MuMu 实机往返验证,以及用无头 Edge 渲染真实面板组件得到的界面截图。

平台兼容性

UP CastPlay

独立实现的局域网媒体投屏 UTS 插件。用 SSDP 发现 DLNA/UPnP MediaRenderer,解析设备描述,再用 SOAP 把媒体地址交给接收端自行取回并控制播放。插件 ID 为 up-castplay,自带一个可直接使用的内置控制面板组件,不依赖任何商业插件或第三方 SDK。

功能对标 DCloud 插件市场插件 id=28205 的公开功能说明(DLNA 投屏、设备发现、播放控制、视频/音频/图片投送、内置面板、选集/清晰度/字幕/倍速、交接续播、诊断能力、系统路由入口、小程序条件支持)。本插件只参考公开的能力边界,未使用其源码、API 命名、组件标签、界面素材、图标或品牌。接口语义、状态模型、事件模型、工程结构均为另行设计:例如用显式 session 取代全局单例,用 requested 区分「命令被接受」与「接收端确认播放」,用 roster / link / clock 三类信号取代回调堆叠。

这是「把媒体 URL 交给电视播放」,不是屏幕镜像、录屏推流或 AirPlay 私有协议。电视必须能直接访问该 URL;手机本地文件可先用 publishLocal() 发布成局域网 URL,带登录态的私有地址与 DRM 加密流不能直接投送。

能力与平台

平台 设备发现 媒体投送与控制 扩展能力 实现与限制
App-Android 5.0+(API21) SSDP M-SEARCH 组播 + 单播响应 支持 内置面板、本地 HTTP 服务、GENA 回调服务、系统投屏设置入口 Kotlin DatagramSocket + HttpURLConnection + ServerSocket;只发送组播、接收单播响应,不加入组播组、不持有 MulticastLock
App-iOS 13+ 同上 支持 同上,系统路由用 AVRoutePickerView Swift BSD UDP socket + URLSession;自动发现需本地网络权限与 Apple Multicast Networking 授权,未获批时用 describeRenderer() 直连
App-HarmonyOS NEXT 同上 支持 内置面板、本地 HTTP 服务、GENA 回调服务 @kit.NetworkKit 的 UDP / TCP socket 与 http;受路由器/系统局域网策略影响
微信小程序 运行时具备 UDP/TCP Socket 时支持 条件支持 基础链路(发现/投送/控制/节目单/协商) 依赖 wx.createUDPSocket / wx.createTCPSocket;手写 HTTP/1.1 over TCP,无本地服务与系统路由能力。未做真机联调,平台矩阵标 u
H5 / Web 与其他小程序 无 无 同步查询与媒体预检 浏览器/沙箱没有等价的 UDP 组播与局域网原生 socket;所有异步操作返回 30002,platformInfo() / probeMedia() 等同步查询仍可用

没有 AirPlay/Google Cast 私有协议、屏幕镜像、DRM 解密与 ContentDirectory 片库浏览;客观原因与尚未验证项见 docs/up-castplay-scope.md。

架构

utssdk/
  wire.uts       平台无关:SSDP / 设备描述 / SOAP / DIDL-Lite / 时钟 /
                 静态预检 / protocolInfo / 倍速 NMTOKEN / GENA 头与事件解析
  session.uts    平台无关:多会话状态机、名册、投送、轮询、节目单、交接、
                 能力协商、本地资源、系统路由、GENA 订阅、信号、脱敏日志
  interface.uts  公共类型与 30001–30016 错误码
  unierror.uts   统一错误对象
  index.uts      默认降级入口(不注入传输)
  web/           降级入口,重导出默认入口
  mp-weixin/     条件入口:运行时探测 Socket 能力后注入传输
  app-android/ app-ios/ app-harmony/
      index.uts              注入传输实现 + 重导出共享核心
      transport.uts          UTS ↔ 原生桥适配
      CastWireBridge.*       UDP 组播 + HTTP(S)
      CastPlayExtras.*       本地 HTTP 服务 / GENA 回调 / 系统路由入口
components/
  up-castplay-panel/         内置控制面板(Vue SFC,独立命名与样式)

协议与业务逻辑只有一份,各端差异被压缩成一个传输对象:{ multicast, http, now, schedule },可选 publish / unpublish / listen / exchange / openSystemRoute。因此 wire.uts / session.uts 可以直接用 Node 跑真实报文的契约测试,而各端入口只做装配。

安装

  1. 把整个 up-castplay 目录放入项目的 uni_modules,内置面板会随插件一起被 uni-app 的 easycom 识别。
  2. 使用 HBuilderX 4.61+,App 端必须制作自定义调试基座后运行到真机;仅热更新页面不会让原生传输生效。
  3. 按 docs/platform-setup.md 配置三端的本地网络权限。手机与接收端必须在同一局域网,且路由器未隔离组播 / 客户端互访;GENA 订阅还要求接收端能回连手机的本地回调端口。
  4. 插件不引入三方依赖,各端 config.json 只有系统框架与版本声明。
<!-- 最简用法:一行组件,会话由宿主页面创建并传入 -->
<up-castplay-panel :session="sessionId" />
import {
  platformInfo, probeMedia, openSession, closeSession,
  scan, haltScan, describeRenderer, listRenderers,
  attach, detach, load, play, pause, stop, seek,
  setVolume, setMute, refresh, snapshot, observe, unobserve, trace,
  setProgram, programState, selectEpisode, stepEpisode,
  selectVariant, setSpeed, setSubtitleTrack,
  exportHandoff, restoreHandoff, negotiateRenderer, capabilities,
  publishLocal, unpublishLocal, openSystemRoute,
  subscribeEvents, cancelEvents
} from '@/uni_modules/up-castplay';

六个平台入口导出同名同签名的 38 个方法与 24 个公共类型。所有异步操作都是 options 回调风格:success / fail 只结算一次,complete 一定执行(即使前一个回调抛错)。errMsg 为 ${action}:fail 说明 (细节),成功结果为 ${action}:ok。

快速上手

// 1. 先确认平台与媒体地址是否值得尝试(纯计算,H5 也可用)
const info = platformInfo();
const probe = probeMedia({ url: 'https://example.com/movie.mp4', kind: 'video' });
if (!info.supported || !probe.castable) return;

// 2. 显式创建会话并订阅信号
const session = openSession({ label: '客厅', pollIntervalMs: 2000 });
observe({ session, handler: signal => console.log(signal.name, signal.text) });

// 3. 发现接收端(或 iOS 未取得组播授权时直接给描述地址)
scan({ session, clearRoster: true, timeoutMs: 6000 });
// describeRenderer({ session, descriptionUrl: 'http://192.168.1.20:8200/rootDesc.xml' });

// 4. 连接、协商能力、投送
attach({ session, rendererKey: listRenderers(session)[0].key });
negotiateRenderer({ session, mime: 'video/mp4' });
// capabilities(session).mimeSupported / timeSeek 可用于提前提示
load({
  session,
  media: { url: 'https://example.com/movie.mp4', title: '示例视频', kind: 'video', mime: 'video/mp4' }
});

// 5. 状态以接收端回报为准
refresh({ session, success: res => console.log(res.transportState, res.position) });

load 的 success 只返回 state: 'requested',表示 SetAVTransportURI → Play 两条 SOAP 命令被接收端接受,不代表画面已出现。真实状态由 GetTransportInfo / GetPositionInfo / GetVolume 或 GENA NOTIFY 的回报更新,可通过 refresh()、snapshot() 或 clock.tick / transport.changed / device.event 信号观察。

进阶能力

需求 做法
选集 / 清晰度 / 字幕 / 倍速 setProgram({ program }) 装载 CastProgram,再用 selectEpisode / stepEpisode / selectVariant / setSubtitleTrack / setSpeed 切换;切换保留当前进度,字幕写进 DIDL-Lite 第二个 res,倍速走 Play(Speed)
断点续播 / 手机→电视交接 const h = exportHandoff(session) 持久化;换设备或重进页面后 restoreHandoff({ handoff: h })
投本地文件 / 内联字幕 publishLocal({ sourcePath }) 或 publishLocal({ sourceText, sourceName, mime }) → 拿到局域网 http:// URL 再投;用完 unpublishLocal()
系统路由 openSystemRoute() 拉起系统公开选择器;platformInfo().systemRoute 为 false 时返回 30002,不会假装已连接
接收端主动回报 设备描述含 eventSubURL 时 subscribeEvents(),状态由 NOTIFY 更新;cancelEvents() 退订,关闭会话也会尽力退订
内置 UI 直接用 <up-castplay-panel :session="sessionId" />;也可只用 API 自建界面,插件不强制绑定 UI

内置面板

up-castplay-panel 是一个播放器级的控制面板,不是只列几个按钮的调试页。面板从 session 读取真实状态,界面分区如下:

区域 内容
头部 标题与能力徽章:真实投屏 / 降级模式(按 platformInfo().supported)
接收端 设备名册,横向滚动的设备卡片(设备名 + 已连接 / 点击连接),点击即 attach();旁边是「扫描」「能力协商」「系统路由」入口,并显示接收端声明支持的 MIME
播放器 当前媒体与集数、播放中徽标、可拖动跳转的进度条(左右显示 mm:ss / 总时长)、播放 / 暂停、停止、刷新、静音、音量条(0–100%,未知时置灰)
画质 · 字幕 · 倍速 流畅 / 高清、中文字幕 / 关闭字幕、0.5x / 1x / 1.5x / 2x,当前选中项高亮
节目单 · 选集 装载示例、上一集 / 下一集、集数按钮网格(当前集高亮)
交接 · 本地资源 · 事件 导出交接 / 恢复续播、发布本地字幕 / 停止本地服务、订阅事件 / 取消订阅,并显示本地服务地址
诊断 可折叠的动作日志行(动作名与结果码)

面板只读状态、不自己造状态:进度与时长来自接收端回报或事件通知,播放态取 transportState === 'playing'(session.uts 的状态映射统一为小写)。未连接、未装载节目单、音量未知时都有明确文案,不会把空状态显示成有数据。

API

完整参数与返回值见 docs/api.md。

分组 方法 说明
查询(同步) platformInfo() / probeMedia(media) / programState(session) / capabilities(session) / exportHandoff(session) 能力矩阵、静态预检、节目单/能力快照、交接凭证;降级平台也可用
会话 openSession(options) → string / closeSession(options) 显式会话;关闭会话停止轮询、清空订阅、尽力退订 GENA
发现 scan / haltScan / describeRenderer / listRenderers(session) 组播搜索、中止、按描述地址直连、读取名册
连接 attach / detach detach({ stopRemote: true }) 才向接收端发 Stop
投送与控制 load / play / pause / stop / seek / setVolume / setMute / setSpeed setVolume 的 0–1 映射为 UPnP 的 0–100
节目单 setProgram / selectEpisode / stepEpisode / selectVariant / setSubtitleTrack 切换统一复用投送链路并保留进度
交接与协商 restoreHandoff / negotiateRenderer 交接凭证恢复;GetProtocolInfo 能力协商
本地与系统 publishLocal / unpublishLocal / openSystemRoute 局域网 HTTP 发布、停止服务、系统路由选择器
状态 refresh / snapshot(session) 主动采样与同步快照
事件 subscribeEvents / cancelEvents GENA 订阅与退订
信号 observe → boolean / unobserve → boolean 注册与移除必须用同一函数引用
诊断 trace(options) → Array<TraceEntry> 只有动作、阶段、耗时、错误码;不含 URL、设备地址、SOAP 正文

状态与信号

CastTransportState:idle → requested(命令被接受)→ playing / paused / stopped / transitioning / error,后几者只在接收端回报后出现。volumeKnown: false 时 snapshot().volume 只是命令目标值,不是真实音量。

信号名:scan.opened / scan.closed、renderer.online / roster.changed、link.opened / link.closed、transport.changed、clock.tick、volume.changed、program.changed、speed.changed、capability.changed、handoff.restored、device.event / device.subscribed、fault。单个订阅者抛错不会阻断其他订阅者或状态机。

错误码

码 含义 常见处理
30001 会话不存在或已关闭 重新 openSession
30002 当前平台不支持真实投屏 改用 App 自定义基座;H5/未适配小程序只能做预检;系统路由未注入时也返回此码
30003 参数不正确 检查 rendererKey / media / position / volume / speed / descriptionUrl
30004 传输层不可用 检查本地网络权限、Wi-Fi、路由器组播隔离
30005 未发现可用接收端 换 searchTarget,或用 describeRenderer 直连
30006 接收端缺少必要的 UPnP 服务 无 AVTransport 不能投屏;无 RenderingControl 时音量动作不可用;无 ConnectionManager 无法协商;无 eventSubURL 无法订阅
30007 媒体地址不可投送 先看 probeMedia().flags 与 advice,本地文件用 publishLocal()
30008 尚未连接接收端 先 attach
30009 SOAP 请求失败或超时 检查设备地址、HTTP 状态、局域网互访
30010 接收端拒绝该控制动作 解析 UPnP Fault 的 errorCode,常见于状态不允许跳转
30011 设备描述文档解析失败 描述地址不是有效的 UPnP root 文档
30012 订阅或诊断参数不正确 检查 handler / maxEntries
30013 节目单状态不正确 未装载节目单、集数越界、档位/字幕轨不存在、交接凭证无节目也无媒体
30014 接收端不支持该能力 协议协商不匹配,或倍速被设备拒绝
30015 本地资源服务不可用 平台未注入 publish、发布失败或缺少 sourcePath / sourceText
30016 事件订阅失败或已过期 本地回调端口不可用、SUBSCRIBE 失败或响应缺 SID

测试

工程根目录执行 npm test。Node 直接加载 .uts 源码(处理条件编译与类型注解)跑契约测试,不需要 HBuilderX:

  • wire.test.mjs:真实 SSDP / 描述 XML / SOAP / DIDL-Lite 报文的线格式;
  • session.test.mjs:注入假传输后的状态机、去重、投送顺序、Fault 映射、一次性回调、脱敏日志;
  • extended.test.mjs:节目单、清晰度、字幕、倍速、交接续播、能力协商、本地资源、系统路由、GENA 订阅与 30013–30016;
  • platform-gate.test.mjs:降级入口的 30002 行为、同步查询与空快照;
  • source-contract.test.mjs:六个入口导出一致性、平台无关性、权限声明、能力矩阵、错误码、独立命名、SFC 绑定;
  • verification-contract.test.mjs:MuMu 夹具与插件源码同步、发布截图合规。

另有 node verification/check-sfc.mjs 用真实 @vue/compiler-sfc 编译示例页与内置组件(需要本地或同工作区已安装 Vue 编译器)。

发布截图分两类,都不是设计稿:一类是无头 Edge 渲染本插件真实的面板组件(设备列表、播放/暂停、进度条、音量都是面板源码的真实结构);一类是 MuMu 12 实机截图,其中包含模拟接收端真实播放视频的画面。生成流程见工程根目录 README.md 与 verification/README.md。

隐私

插件不采集、不上传任何数据。设备描述与 SOAP 控制报文只在业务手机与用户选择的局域网接收端之间传输;publishLocal() 起的 HTTP 服务只监听局域网、只提供业务显式发布的文件与字幕文本,unpublishLocal() 即关闭;诊断日志只记录动作、阶段、耗时与错误码,不含媒体地址或设备地址。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android: INTERNET、ACCESS_NETWORK_STATE、ACCESS_WIFI_STATE、CHANGE_WIFI_MULTICAST_STATE;iOS: 本地网络用途说明与局域网访问,自动组播发现需 Apple Multicast Networking 授权;鸿蒙: ohos.permission.INTERNET、ohos.permission.GET_NETWORK_INFO。GENA 事件订阅需要接收端能回连手机的本地回调端口,系统路由选择器只调用系统公开入口,不实现私有投屏协议

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集、不上传任何数据;设备描述与 SOAP 控制报文只在业务手机与用户选择的局域网接收端之间传输;publishLocal() 起的 HTTP 服务只监听局域网、只提供业务显式发布的文件与字幕文本,不对外网开放,停止会话/调用 unpublishLocal() 即关闭;诊断日志只记录动作、阶段、耗时与错误码

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

许可协议

MIT License

Copyright (c) 2026 xyito

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

暂无用户评论。