更新记录
1.2.3(2026-03-15)
- 修复 iOS 端对 DLNA 设备描述 XML 的服务解析,避免部分设备遗漏
AVTransport/RenderingControl控制服务。 - 修复 iOS 端特殊
controlURL的绝对地址拼接,兼容 Redmi 电视等返回_urn:...路径的设备。 - 补充 iOS 侧设备描述与服务解析日志,便于排查局域网投屏兼容性问题。
1.2.2(2026-03-13)
dlnaSeek支持非负小数秒,Android / iOS 会向接收端发送带毫秒的HH:MM:SS.xxx时间格式。- 接收端进度/时长解析同步支持小数秒,playground 的 seek 输入与进度展示一并适配。
- 修复 iOS UTS 桥接的
number -> Double参数转换问题,避免seekAsync编译失败。
1.2.1(2026-03-13)
- iOS 插件补充
UTS.entitlements,声明com.apple.developer.networking.multicast以支持 SSDP 组播发现。 - README 补充 iOS multicast capability 说明与排查提示。
平台兼容性
uni-app(4.87)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | - | - | - | - | √ | √ | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(4.87)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | √ | √ | - | - |
hans-dlna
hans-dlna 是一个用于局域网内发现 DLNA 设备并执行媒体投放控制的 UTS 插件,当前实现覆盖 Android / iOS,提供设备发现、设备选择、播放控制、接收端状态监听、事件回调和日志开关。
文档中的 API 与类型以 utssdk/interface.uts 为准。
平台支持
| 运行时 | Android | iOS | Harmony |
|---|---|---|---|
| uni-app | √ | √ | - |
| uni-app x | √ | √ | - |
说明:
- Android / iOS 为当前完整实现。
- Harmony 提供了可编译的 stub。
- Harmony 上功能型 API 会以
90010008失败返回;监听注册函数返回占位listenerId,offDlnaListener为 no-op。
能力概览
- 生命周期:
dlnaInit、dlnaDestroy - 设备发现:
dlnaStartDiscovery、dlnaStopDiscovery、dlnaGetDevices - 设备选择:
dlnaSelectDevice - 播放控制:
dlnaPlay、dlnaPause、dlnaStop、dlnaSeek - 接收端监听:
dlnaStartReceiverMonitor、dlnaStopReceiverMonitor、dlnaGetReceiverStatus - 事件回调:
onDlnaDeviceFound、onDlnaError、onDlnaReceiverEvent、offDlnaListener - 调试能力:
setDlnaLogEnabled、isDlnaLogEnabled
使用前提
- 手机与目标 DLNA 设备处于同一局域网。
- 传入的媒体
url必须能被目标设备直接访问。 - 建议先注册监听并调用
dlnaInit,再开始扫描设备。 - 若使用接收端监听,建议先调用
dlnaSelectDevice选择目标设备。
引入方式
按需从插件入口导入:
import {
dlnaInit,
dlnaDestroy,
dlnaStartDiscovery,
dlnaStopDiscovery,
dlnaGetDevices,
dlnaSelectDevice,
dlnaPlay,
dlnaPause,
dlnaStop,
dlnaSeek,
dlnaStartReceiverMonitor,
dlnaStopReceiverMonitor,
dlnaGetReceiverStatus,
onDlnaDeviceFound,
onDlnaError,
onDlnaReceiverEvent,
offDlnaListener,
setDlnaLogEnabled,
isDlnaLogEnabled,
DlnaDevice,
DlnaMedia,
DlnaFail,
DlnaReceiverStatus,
DlnaReceiverEvent,
DlnaListenerId,
DlnaInitOptions,
DlnaStartDiscoveryOptions,
DlnaSelectDeviceOptions,
DlnaPlayOptions,
DlnaSeekOptions,
DlnaStartReceiverMonitorOptions
} from '@/uni_modules/hans-dlna'
所有 options / result / listener 类型都可以直接从插件入口导入。
快速开始
import {
dlnaInit,
dlnaDestroy,
dlnaStartDiscovery,
dlnaStopDiscovery,
dlnaSelectDevice,
dlnaPlay,
onDlnaDeviceFound,
onDlnaError,
offDlnaListener,
DlnaDevice,
DlnaMedia,
DlnaFail,
DlnaListenerId,
DlnaInitOptions,
DlnaDestroyOptions,
DlnaStartDiscoveryOptions,
DlnaStopDiscoveryOptions,
DlnaSelectDeviceOptions,
DlnaPlayOptions
} from '@/uni_modules/hans-dlna'
var deviceFoundListenerId : DlnaListenerId | null = null
var errorListenerId : DlnaListenerId | null = null
const devices = ref<Array<DlnaDevice>>([])
const selectedDeviceId = ref('')
onLoad(() => {
deviceFoundListenerId = onDlnaDeviceFound((res : DlnaDevice) => {
const idx = devices.value.findIndex((item : DlnaDevice) : boolean => item.id == res.id)
if (idx >= 0) {
devices.value[idx] = res
} else {
devices.value.push(res)
}
})
errorListenerId = onDlnaError((err : DlnaFail) => {
console.error('dlna error', err.errCode, err.errMsg)
})
const initOptions : DlnaInitOptions = {
success: (_res : UTSJSONObject) => {
dlnaStartDiscovery({ timeoutMs: 8000 } as DlnaStartDiscoveryOptions)
}
}
dlnaInit(initOptions)
})
function chooseDevice(deviceId : string) {
const options : DlnaSelectDeviceOptions = {
deviceId: deviceId,
success: (res : DlnaDevice) => {
selectedDeviceId.value = res.id
},
fail: (err : DlnaFail) => {
console.error('select device failed:', err)
}
}
dlnaSelectDevice(options)
}
function play(url : string) {
if (selectedDeviceId.value.length == 0) {
return
}
const media : DlnaMedia = {
url: url,
title: 'demo',
mimeType: 'video/mp4'
}
const options : DlnaPlayOptions = {
media: media,
success: (res : UTSJSONObject) => {
console.log('play success:', res)
},
fail: (err : DlnaFail) => {
console.error('play failed:', err)
}
}
dlnaPlay(options)
}
onUnload(() => {
dlnaStopDiscovery({} as DlnaStopDiscoveryOptions)
if (deviceFoundListenerId != null) {
offDlnaListener(deviceFoundListenerId!)
deviceFoundListenerId = null
}
if (errorListenerId != null) {
offDlnaListener(errorListenerId!)
errorListenerId = null
}
dlnaDestroy({} as DlnaDestroyOptions)
})
API 说明
所有 API 都是回调风格:
success(res):成功回调fail(err):失败回调,错误类型为DlnaFailcomplete(result):完成回调,成功或失败都会触发
生命周期
dlnaInit(options)
- 作用:初始化插件内部状态和底层桥接。
- 成功返回:
{ ok: true }
dlnaDestroy(options)
- 作用:销毁插件并清理已选择设备、监听状态与内部缓存。
- 成功返回:
{ ok: true }
发现与设备管理
dlnaStartDiscovery({ timeoutMs? })
- 作用:开始扫描局域网内 DLNA 设备。
- 默认值:
timeoutMs = 5000 - 成功返回:
{ discovering: true } - 事件:扫描过程中会通过
onDlnaDeviceFound持续回调设备。
dlnaStopDiscovery(options)
- 作用:停止扫描。
- 成功返回:
{ discovering: false }
dlnaGetDevices(options)
- 作用:获取当前已缓存的设备列表。
- 成功返回:
{ devices: DlnaDevice[] }
dlnaSelectDevice({ deviceId })
- 作用:选择后续播放控制和状态监听的目标设备。
- 成功返回:
DlnaDevice - 常见失败:
deviceId为空deviceId对应设备不存在
播放控制
dlnaPlay({ media })
- 作用:向当前已选择设备发送播放指令。
- 必填:
media.url - 默认值:
media.mimeType = 'video/mp4' - 成功返回:
{ state: 'PLAYING' }
dlnaPause(options)
- 作用:向当前已选择设备发送暂停指令。
- 成功返回:
{ state: 'PAUSED_PLAYBACK' }
dlnaStop(options)
- 作用:向当前已选择设备发送停止指令。
- 成功返回:
{ state: 'STOPPED' }
dlnaSeek({ positionSec })
- 作用:向当前已选择设备发送跳转指令,按绝对秒数定位播放进度。
- 必填:
positionSec,非负数字,单位为秒;支持小数秒,例如90.5。 - 成功返回:
{ positionSec: number } - 说明:
- 插件内部会优先使用
REL_TIME,失败时回退到ABS_TIME。 - 若目标设备或当前媒体不支持 seek,通常会以
90010005返回控制失败。
- 插件内部会优先使用
接收端监听
dlnaStartReceiverMonitor({ intervalMs?, idleIntervalMs?, includeVolume?, preferSubscribe? })
- 作用:开始监听当前接收端的连接状态、播放状态、进度与音量信息。
- 默认值:
intervalMs = 1000idleIntervalMs = 3000includeVolume = falsepreferSubscribe = false
- 成功返回:
{ monitoring: true, mode: 'poll' | 'subscribe' } - 前置条件:必须已调用
dlnaSelectDevice
说明:
- 当
preferSubscribe: true时,Android / iOS 会优先尝试订阅式状态更新;若设备不支持订阅,或订阅失效,会自动回退到轮询兜底。 success返回的mode是启动时的当前模式;运行过程中实际模式可能在poll与subscribe之间切换,最终以onDlnaReceiverEvent或dlnaGetReceiverStatus返回的status.mode为准。
dlnaStopReceiverMonitor(options)
- 作用:停止监听。
- 成功返回:
{ monitoring: false }
dlnaGetReceiverStatus(options)
- 作用:获取最近一次接收端状态;若当前没有缓存状态,会主动请求一次设备状态。
- 成功返回:
DlnaReceiverStatus - 前置条件:必须已调用
dlnaSelectDevice
onDlnaReceiverEvent(callback)
- 作用:监听接收端状态事件。
- 返回值:
listenerId
事件类型:
STATUS_CHANGEDPROGRESSSEEKEDDISCONNECTEDRECONNECTEDENDED
补充说明:
SEEKED表示接收端进度发生明显跳变,适合响应拖动进度条等场景。ENDED为 best-effort 判定,依赖设备返回的状态和进度信息,不保证所有 DLNA 设备行为完全一致。- 若
includeVolume为false,DlnaReceiverStatus.volume和DlnaReceiverStatus.muted可能为空。
事件监听
onDlnaDeviceFound(callback)
- 作用:监听发现到的设备。
- 返回值:
listenerId
onDlnaError(callback)
- 作用:监听插件内部错误和解析失败等异常。
- 返回值:
listenerId
offDlnaListener(listenerId)
- 作用:按
listenerId解绑监听器。
日志控制
setDlnaLogEnabled(enabled: boolean)
- 作用:设置插件日志开关。
isDlnaLogEnabled()
- 作用:读取当前插件日志开关状态。
示例:
setDlnaLogEnabled(true)
console.log('plugin log enabled:', isDlnaLogEnabled())
类型定义
错误类型
type DlnaErrorCode =
| 90010001 // 参数错误
| 90010002 // 未初始化
| 90010003 // 未选择设备
| 90010004 // 发现失败
| 90010005 // SOAP 调用失败
| 90010006 // 网络不可用
| 90010007 // 请求超时
| 90010008 // 平台不支持
interface DlnaFail extends IUniError {
errCode: DlnaErrorCode
}
设备与媒体类型
type DlnaDevice = {
id: string
usn: string
st: string
location: string
friendlyName: string
manufacturer?: string
modelName?: string
avTransportControlURL?: string
renderingControlURL?: string
}
type DlnaMedia = {
url: string
title?: string
mimeType?: string
}
type DlnaTransportState =
| 'STOPPED'
| 'PLAYING'
| 'PAUSED_PLAYBACK'
| 'TRANSITIONING'
| 'NO_MEDIA_PRESENT'
| 'UNKNOWN'
接收端监听类型
type DlnaMonitorMode = 'subscribe' | 'poll'
type DlnaReceiverEventType =
| 'STATUS_CHANGED'
| 'PROGRESS'
| 'SEEKED'
| 'DISCONNECTED'
| 'RECONNECTED'
| 'ENDED'
type DlnaReceiverStatus = {
deviceId: string
connected: boolean
mode: DlnaMonitorMode
transportState: DlnaTransportState
positionSec?: number
durationSec?: number
volume?: number
muted?: boolean
ended?: boolean
updatedAt: number
}
type DlnaReceiverEvent = {
type: DlnaReceiverEventType
status: DlnaReceiverStatus
prevStatus?: DlnaReceiverStatus
message?: string
}
监听器类型
type DlnaListenerId = number
type DlnaOnDeviceFound = (callback: (res: DlnaDevice) => void) => DlnaListenerId
type DlnaOnError = (callback: (res: DlnaFail) => void) => DlnaListenerId
type DlnaOnReceiverEvent = (callback: (res: DlnaReceiverEvent) => void) => DlnaListenerId
type DlnaOffListener = (listenerId: DlnaListenerId) => void
type SetDlnaLogEnabled = (enabled: boolean) => void
type IsDlnaLogEnabled = () => boolean
API 选项类型
type DlnaInitOptions = {
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaDestroyOptions = {
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaStartDiscoveryOptions = {
timeoutMs?: number
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaStopDiscoveryOptions = {
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaGetDevicesResult = {
devices: Array<DlnaDevice>
}
type DlnaGetDevicesOptions = {
success?: (res: DlnaGetDevicesResult) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaSelectDeviceOptions = {
deviceId: string
success?: (res: DlnaDevice) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaPlayOptions = {
media: DlnaMedia
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaPauseOptions = {
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaStopOptions = {
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaStartReceiverMonitorOptions = {
intervalMs?: number
idleIntervalMs?: number
includeVolume?: boolean
preferSubscribe?: boolean
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaStopReceiverMonitorOptions = {
success?: (res: UTSJSONObject) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
type DlnaGetReceiverStatusOptions = {
success?: (res: DlnaReceiverStatus) => void
fail?: (res: DlnaFail) => void
complete?: (res: any) => void
}
API 函数类型
type DlnaInit = (options: DlnaInitOptions) => void
type DlnaDestroy = (options: DlnaDestroyOptions) => void
type DlnaStartDiscovery = (options: DlnaStartDiscoveryOptions) => void
type DlnaStopDiscovery = (options: DlnaStopDiscoveryOptions) => void
type DlnaGetDevices = (options: DlnaGetDevicesOptions) => void
type DlnaSelectDevice = (options: DlnaSelectDeviceOptions) => void
type DlnaPlay = (options: DlnaPlayOptions) => void
type DlnaPause = (options: DlnaPauseOptions) => void
type DlnaStop = (options: DlnaStopOptions) => void
type DlnaStartReceiverMonitor = (options: DlnaStartReceiverMonitorOptions) => void
type DlnaStopReceiverMonitor = (options: DlnaStopReceiverMonitorOptions) => void
type DlnaGetReceiverStatus = (options: DlnaGetReceiverStatusOptions) => void
错误码
90010001:参数错误,例如deviceId为空、media.url为空、设备不存在90010002:未初始化90010003:未选择设备90010004:设备发现失败90010005:SOAP 调用失败或接收端状态解析失败90010006:网络不可用或初始化 / 销毁阶段底层异常90010007:请求超时90010008:平台不支持
权限与隐私说明
Android
插件已在 utssdk/app-android/AndroidManifest.xml 中声明以下权限:
android.permission.INTERNETandroid.permission.ACCESS_NETWORK_STATEandroid.permission.ACCESS_WIFI_STATEandroid.permission.CHANGE_WIFI_MULTICAST_STATE
iOS
插件已在 utssdk/app-ios/Info.plist 中声明:
NSLocalNetworkUsageDescriptionutssdk/app-ios/UTS.entitlements中的com.apple.developer.networking.multicast
说明:
- 首次访问本地网络时,系统会弹出局域网权限提示。
- DLNA 设备发现依赖 SSDP 组播,iOS 侧需要同时开启 multicast capability。
- 若真机仍无法发送
M-SEARCH,请检查当前签名 / provisioning profile 是否允许该 capability。 - 如果宿主应用需要自定义提示文案,可以在宿主工程侧覆盖该配置。
隐私声明
插件仅在局域网内发现 DLNA 设备并发送媒体投放控制指令,不采集或上传个人隐私数据。
调试建议
- 推荐先开启插件日志:
setDlnaLogEnabled(true) - 播放目标优先选择电视等真实 DLNA 接收端设备
- 接入接收端监听时,建议在业务页面同时展示连接状态、播放状态、进度和音量信息,便于定位设备兼容性问题

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 299
赞赏 0
下载 11532623
赞赏 1904
赞赏
京公网安备:11010802035340号