更新记录

1.1.0(2026-10-23)

-


平台兼容性

uni-app(5.01)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
√ √ - - √ √ √ √ √
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(5.01)

Chrome Safari Android iOS 鸿蒙 微信小程序
× × √ √ √ -

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× √ × √

wb-dlna-cast · 局域网投屏与电视遥控器

把手机上的视频 / 音乐 / 图片投到同一 Wi-Fi 下的电视、投影仪、电视盒子,并像遥控器一样控制播放、进度和音量。

Android 用 Kotlin、iOS 用 Swift 原生实现,不依赖 Cling / CocoaPods 等任何三方库,云打包可直接通过。

深色主题

特性

  • 设备发现:SSDP 组播搜索 + NOTIFY 通告,自动过滤掉不支持投放的设备,只留真正能接流的渲染器
  • 投放媒体:视频 / 音乐 / 图片,自动按扩展名生成正确的 MIME 与 DIDL-Lite 元数据
  • 播放控制:播放、暂停、继续、停止、拖动进度、音量、静音
  • 状态同步:每 2 秒回读播放进度与传输状态,暂停 / 播放由电视实际状态决定,不是本地猜的
  • 总时长四级兜底:设备不给也能显示(详见下方说明)
  • 事件驱动:设备上下线、状态变化、错误、诊断日志全部可监听
  • 零三方依赖:Android 只用 java.net + org.json,iOS 只用 Foundation,不引入任何 jar / pod
  • 原生混编:DlnaCastNative.kt / DlnaCastNative.swift 直接参与编译,改原生逻辑不用重新打基座

界面示例

浅色主题 设备发现 运行日志
见 demo pages/index/index.vue 底部折叠面板

截图来自配套 demo(pages/index/index.vue),深色 / 浅色两套主题,可直接拿来改。

支持的生态环境

平台 支持 说明
uni-app(Vue3) ✅ HBuilderX 4.25+,<script setup> 可直接用
uni-app(Vue2) ✅ 同上,需 HBuilderX 4.25+
uni-app x ✅ 同上,接口完全一致
Android App ✅ Android 5.0(API 21)及以上真机
iOS App ✅ iOS 12.0 及以上真机
H5 / 小程序 / 鸿蒙 / 快应用 ❌ 依赖原生 UDP 组播与 socket,端上无法实现

必须用真机:iOS 模拟器、Android 模拟器都收不到局域网组播,只能看到空设备列表。

运行环境要求

  • HBuilderX 4.25 及以上(插件用了 UTS 原生混编)
  • 手机与电视在同一局域网(同一 Wi-Fi,或同网段的有线 / 无线互通)
  • 电视已开启「投屏 / 多屏互动 / DLNA」功能

快速开始

1. 导入插件

把 uni_modules/wb-dlna-cast 整个目录放进项目根目录的 uni_modules 下(HBuilderX 插件市场一键导入即可)。

2. 配置权限

Android / iOS 的权限说明见下方权限说明,插件已自带清单,绝大多数情况不需要手改。

3. 最少可用代码

import * as DlnaCast from '@/uni_modules/wb-dlna-cast'

// 搜索(不传 timeout 默认 5000ms)
const devices = await DlnaCast.startScan(6000)
if (!devices.length) {
  console.log('没有发现设备')
  return
}

// 选一台连接
await DlnaCast.connect(devices[0])

// 投放(type: video | audio | image)
await DlnaCast.play('https://example.com/movie.mp4', 'video', '片名', '')

// 监听状态(进度、播放中、音量…)
DlnaCast.onStateChange((state) => {
  console.log(state.position, state.duration, state.playing)
})

API

设备发现

方法 参数 返回 说明
startScan(timeout?) timeout: 毫秒,默认 5000 Promise<DlnaDevice[]> 搜索并返回这段时间内发现的设备
stopScan() — void 提前结束本次搜索窗口(不会断开已连接设备)
onDeviceFound(cb) cb: (device) => void void 搜索过程中陆续发现设备时回调
onDeviceLost(cb) cb: (udn) => void void 设备下线 / 30 秒无通告

连接与投放

方法 参数 返回 说明
connect(device) device: DlnaDevice Promise<void> 选中设备,内部会同步一次电视当前音量
disconnect() — Promise<void> 停止播放并断开
play(url, type, title?, cover?) url: 直链;type: video/audio/image Promise<void> 先 SetAVTransportURI 再 Play,两步都成功才 resolve

播放控制

方法 参数 返回 说明
pause() — Promise<void> 暂停
resume() — Promise<void> 继续
stop() — Promise<void> 停止播放(不断开设备)
seek(seconds) 目标秒数 Promise<void> 按 REL_TIME 拖动,超出范围会被设备拒绝
setVolume(volume) 0–100 Promise<void> 自动收敛到 0–100
setMute(muted) boolean Promise<void> 静音开关

状态与事件

方法 返回 说明
getState() Promise<CastState> 取当前状态快照
getStateSync() CastState 同步取,适合在渲染前读一次
onStateChange(cb) void 每 2 秒随进度轮询推送
onError(cb) void 渲染器返回 UPnP 错误码时触发,参数是错误描述
onLog(cb) void 诊断日志([SOAP] xxx),排查问题首选
offStateChange() 等 void 清空该事件的全部监听
release() void 页面卸载时释放 socket 与定时器

offXxx() 传不传 callback 效果一致:都会清空该事件下的全部监听。

数据结构

type DlnaDevice = {
  udn: string          // 设备唯一标识
  name: string         // 电视上显示的名称,如「客厅的小米电视」
  address: string      // 设备描述 XML 的主机地址
  port: number
  type: string         // renderer
  location: string     // 描述 XML 完整地址
  avtControl: string   // AVTransport 控制端点(播放控制)
  rcControl: string    // RenderingControl 控制端点(音量/静音)
}

type CastState = {
  connected: boolean
  device: DlnaDevice | null
  url: string
  type: string         // video / audio / image / ''
  position: number     // 当前进度(秒)
  duration: number     // 总时长(秒),拿不到时为 0
  playing: boolean
  volume: number       // 0–100
  muted: boolean
}

权限说明

插件已自带清单文件,正常情况无需手动配置。如果宿主工程做过权限裁剪、或云打包版本较老没合并插件清单,按下面手动补一次。

Android

插件路径:utssdk/app-android/AndroidManifest.xml

权限 是否必需 用途
android.permission.INTERNET ✅ 必需 SSDP 收发、拉取设备描述 XML、下发 SOAP 控制
android.permission.ACCESS_NETWORK_STATE ✅ 必需 判断网络 / Wi-Fi 可用性
android.permission.ACCESS_WIFI_STATE ✅ 必需 获取 WifiManager 以申请 MulticastLock
android.permission.CHANGE_WIFI_MULTICAST_STATE ✅ 必需 没有它 Wi-Fi 休眠后收不到组播,表现为「第一次能搜到,之后再也搜不到」
android.permission.NEARBY_WIFI_DEVICES ⭕ 建议 Android 13+ 部分机型发现局域网设备需要,已标注 neverForLocation(不索取位置)

手动补到 manifest.json:

{
  "app-plus": {
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.INTERNET\"/>",
          "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
          "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
          "<uses-permission android:name=\"android.permission.CHANGE_WIFI_MULTICAST_STATE\"/>",
          "<uses-permission android:name=\"android.permission.NEARBY_WIFI_DEVICES\"/>"
        ]
      }
    }
  }
}

以上都是普通权限,不会弹授权框,用户无感。

iOS

插件路径:utssdk/app-ios/info.plist(会自动合并进宿主 Info.plist)

Key 是否必需 用途
NSLocalNetworkUsageDescription ✅ 必需 iOS 14+ 访问局域网(SSDP / HTTP)必须声明,否则搜不到任何设备
NSAppTransportSecurity → NSAllowsLocalNetworking ⭕ 建议 允许读取局域网内的 http 媒体(探测总时长用),不影响公网 ATS 策略
<key>NSLocalNetworkUsageDescription</key>
<string>用于发现同一 Wi-Fi 下的电视、投影仪等投屏设备</string>
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>

首次搜索时会弹「允许查找并连接本地网络设备」,必须点允许,否则系统直接丢弃 SSDP 报文。

不需要苹果审批的 com.apple.developer.networking.multicast entitlement,本地网络权限就够了。

隐私

插件不采集、不上传任何数据:设备信息、播放地址、日志全部只在本机内存中处理,出网请求只有两类——局域网内设备描述 XML、以及你传入的媒体直链。

常见问题

Q:搜不到设备?

  1. 确认手机和电视在同一 Wi-Fi / 同一网段(手机用 4G、电视走有线但路由器隔离,都搜不到)
  2. 电视端要开启「投屏 / 多屏互动 / DLNA」
  3. iOS 检查「设置 → 隐私 → 本地网络」里本 App 是否被关闭
  4. 路由器开了 AP 隔离 / 访客网络会阻断组播,关掉再试
  5. 打开 onLog,正常应该看到 M-SEARCH 已发送 和 发现设备: xxx

Q:能搜到,但点播放报错(如 701 / 714 / 716)?

701 是「传输状态不允许该动作」(设备还没准备好),714 是「跳转目标非法」(seek 超长),716 是「资源找不到」——多数是媒体地址电视访问不到,检查直链是否可公网访问、是否需要 Referer。

Q:投屏后电视黑屏 / 一直在转圈?

DLNA 是「电视自己去拉流」,手机只负责发地址。所以:

  • file://、blob:、手机本地相册地址投不过去
  • 需要能被电视访问的地址:公网 http/https,或局域网里另一个 http 服务
  • 部分电视只支持 H.264 + AAC 的 mp4,mkv / hevc 会黑屏

Q:为什么没有显示总时长?

插件按四级顺序取:TrackDuration → DIDL 的 <res duration> → GetMediaInfo 的 MediaDuration → 发送端本地探测(MediaMetadataRetriever / AVURLAsset)。青蜂鸟这类廉价投屏接收器前三级全不给,靠第四级兜底;四级都失败时 UI 显示「未知时长」,不显示假进度。日志里会打印 探测到总时长 Ns。HLS(m3u8)属于分片流,系统探测 API 拿不到总时长,属已知限制。

Q:遥控器的方向键为什么是调音量?

通用 DLNA 协议只定义了播放、进度、音量,没有标准的方向键注入。所以方向键做了语义映射:↑↓ 音量、←→ 快退快进 10 秒、OK 播放暂停。如果电视厂商有私有按键服务(部分是 X_ 扩展动作),需要单独适配。

Q:切到后台 / 息屏还能继续投吗?

可以。播放是电视在执行,App 只负责发控制指令。但 Android 上如果系统把 App 进程回收,就收不到状态推送了,需要自己加前台服务保活。

Q:能投本地视频文件吗?

需要先把文件暴露成一个局域网 http 地址(内置 http server 或上传到服务器),再把该地址传给 play()。插件只接受 url 字符串。

已知限制

  • 只支持 DLNA / UPnP 的 MediaRenderer 设备;AirPlay、Miracast、Chromecast 不走这套协议
  • 组播依赖同一广播域,跨网段 / VLAN 需要路由器配置组播转发
  • 部分设备的 GetPositionInfo 返回 NOT_IMPLEMENTED,此时进度条退化为「未知时长」,播放控制仍正常
  • 单设备实例:同一时间只维护一个连接,切换设备请先 disconnect()

更新日志

1.1.0(2026-10-23)

  • 新增:总时长四级兜底(TrackDuration → DIDL res@duration → GetMediaInfo → 发送端本地探测),兼容青蜂鸟等不返回时长的投屏接收器
  • 新增:连接设备时自动同步电视当前音量,音量条不再从 0 起步
  • 新增:配套 demo 改为深色 / 浅色双主题,内置拟物电视遥控器
  • 优化:时间解析兼容带毫秒的格式(如 00:03:20.000)
  • 优化:同时支持 uni-app 与 uni-app x,Android Kotlin / iOS Swift 原生混编,零三方依赖
  • 优化:完善 Android / iOS 权限声明与本地网络访问说明
  • 修复:部分设备播放进度、总时长解析异常
  • 修复:多轮云打包编译问题(Swift 闭包默认参数、NSNumber / Number 数值参数、JSON.parse 可选值)

1.0.0(2026-10-22)

  • 首个版本:SSDP 局域网设备发现
  • 支持视频 / 音乐 / 图片投放
  • 支持播放、暂停、继续、停止、拖动进度、音量、静音
  • 支持播放状态轮询与设备上下线事件

完整日志见插件根目录 changelog.md。

协议

MIT

隐私、权限声明

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

Android:INTERNET、ACCESS_NETWORK_STATE、ACCESS_WIFI_STATE、CHANGE_WIFI_MULTICAST_STATE、NEARBY_WIFI_DEVICES(neverForLocation,不索取位置);iOS:NSLocalNetworkUsageDescription(本地网络)、NSAllowsLocalNetworking(读取局域网 http 媒体)。均为局域网投屏必需,不涉及位置、相机、通讯录、相册等敏感权限,Android 侧均为普通权限、不弹授权框。

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

插件不采集、不上传任何数据。设备发现与播放在同一局域网内通过 SSDP / HTTP(S) 完成,出网请求仅两类:局域网内设备的描述 XML,以及使用者传入的媒体直链。

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

无

暂无用户评论。