更新记录

1.3.1(2026-05-20)

  • 新增 dlnaGetCurrentWifi 当前 Wi-Fi 信息读取 API,返回 SSID、BSSID、本机 IP、网关与信号信息等。
  • 新增 dlnaRequestWifiPermission 权限请求 API,Android 支持主动申请读取 Wi-Fi 所需定位权限。
  • 优化 Android 当前 Wi-Fi 获取逻辑,transportInfo 不可用时自动降级到兼容路径。
  • 补充 iOS Access Wi-Fi InformationMulticast Networking entitlement 说明。

1.3.0(2026-05-14)

  • 新增 dlnaResume 恢复播放 API,暂停后仅发送 AVTransport#Play,避免重复 SetAVTransportURI 导致重新投屏。
  • playground 播放按钮支持暂停态自动恢复播放。

1.2.4(2026-05-13)

  • 新增 Android / iOS 音量控制能力:dlnaSetVolumedlnaSetMute
  • playground 新增预设音量按钮、步进音量按钮和静音切换入口。
查看更多

平台兼容性

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,提供当前 Wi-Fi 信息读取、设备发现、设备选择、播放控制、音量控制、接收端状态监听、事件回调和日志开关。

文档中的 API 与类型以 utssdk/interface.uts 为准。

平台支持

运行时 Android iOS Harmony
uni-app -
uni-app x -

说明:

  • Android / iOS 为当前完整实现。
  • Harmony 提供了可编译的 stub。
  • Harmony 上功能型 API 会以 90010008 失败返回;监听注册函数返回占位 listenerIdoffDlnaListener 为 no-op。

能力概览

  • 生命周期:dlnaInitdlnaDestroy
  • 当前网络:dlnaGetCurrentWifi
  • 权限请求:dlnaRequestWifiPermission
  • 设备发现:dlnaStartDiscoverydlnaStopDiscoverydlnaGetDevices
  • 设备选择:dlnaSelectDevice
  • 播放控制:dlnaPlaydlnaResumedlnaPausedlnaStopdlnaSeek
  • 音量控制:dlnaSetVolumedlnaSetMute
  • 接收端监听:dlnaStartReceiverMonitordlnaStopReceiverMonitordlnaGetReceiverStatus
  • 事件回调:onDlnaDeviceFoundonDlnaErroronDlnaReceiverEventoffDlnaListener
  • 调试能力:setDlnaLogEnabledisDlnaLogEnabled

使用前提

  1. 手机与目标 DLNA 设备处于同一局域网。
  2. 传入的媒体 url 必须能被目标设备直接访问。
  3. 建议先注册监听并调用 dlnaInit,再开始扫描设备。
  4. 若使用接收端监听,建议先调用 dlnaSelectDevice 选择目标设备。

引入方式

按需从插件入口导入:

import {
  dlnaInit,
  dlnaDestroy,
  dlnaStartDiscovery,
  dlnaStopDiscovery,
  dlnaGetCurrentWifi,
  dlnaRequestWifiPermission,
  dlnaGetDevices,
  dlnaSelectDevice,
  dlnaPlay,
  dlnaResume,
  dlnaPause,
  dlnaStop,
  dlnaSeek,
  dlnaSetVolume,
  dlnaSetMute,
  dlnaStartReceiverMonitor,
  dlnaStopReceiverMonitor,
  dlnaGetReceiverStatus,
  onDlnaDeviceFound,
  onDlnaError,
  onDlnaReceiverEvent,
  offDlnaListener,
  setDlnaLogEnabled,
  isDlnaLogEnabled,
  DlnaDevice,
  DlnaMedia,
  DlnaWifiInfo,
  DlnaFail,
  DlnaReceiverStatus,
  DlnaReceiverEvent,
  DlnaListenerId,
  DlnaInitOptions,
  DlnaGetCurrentWifiOptions,
  DlnaRequestWifiPermissionOptions,
  DlnaStartDiscoveryOptions,
  DlnaSelectDeviceOptions,
  DlnaPlayOptions,
  DlnaResumeOptions,
  DlnaSeekOptions,
  DlnaSetVolumeOptions,
  DlnaSetMuteOptions,
  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):失败回调,错误类型为 DlnaFail
  • complete(result):完成回调,成功或失败都会触发

生命周期

dlnaInit(options)

  • 作用:初始化插件内部状态和底层桥接。
  • 成功返回:{ ok: true }

dlnaDestroy(options)

  • 作用:销毁插件并清理已选择设备、监听状态与内部缓存。
  • 成功返回:{ ok: true }

当前 Wi-Fi

dlnaGetCurrentWifi(options)

  • 作用:读取当前连接的 Wi-Fi 信息,用于确认手机与 DLNA 设备是否处于同一局域网。
  • 成功返回:DlnaWifiInfo
  • Android:读取 SSID/BSSID 需要定位权限;若权限不足会以 90010009 失败返回。
  • iOS:使用 NEHotspotNetwork.fetchCurrent,需要 Access Wi-Fi Information entitlement,并受系统定位/网络权限策略限制;权限或 entitlement 不满足时会以 90010009 失败返回。
  • 常见失败:
    • 90010009:权限不足或 iOS entitlement/定位条件不满足
    • 90010010:当前未连接 Wi-Fi
  • 90010006:网络服务不可用或返回数据异常

dlnaRequestWifiPermission(options)

  • 作用:请求读取当前 Wi-Fi 所需的定位权限。
  • Android:会主动弹出系统权限框。
  • iOS:返回成功占位;真正读取仍受系统授权与 entitlement 限制。

发现与设备管理

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

dlnaResume(options)

  • 作用:向当前已选择设备发送恢复播放指令,不重新设置媒体地址。
  • 成功返回:{ state: 'PLAYING' }
  • 说明:暂停后恢复播放时建议使用此 API,避免再次调用 SetAVTransportURI 导致接收端重新加载媒体。

dlnaPause(options)

  • 作用:向当前已选择设备发送暂停指令。
  • 成功返回:{ state: 'PAUSED_PLAYBACK' }

dlnaStop(options)

  • 作用:向当前已选择设备发送停止指令。
  • 成功返回:{ state: 'STOPPED' }

dlnaSeek({ positionSec })

  • 作用:向当前已选择设备发送跳转指令,按绝对秒数定位播放进度。
  • 必填:positionSec,非负数字,单位为秒;支持小数秒,例如 90.5
  • 成功返回:{ positionSec: number }
  • 说明:
    • 插件内部会优先使用 REL_TIME,失败时回退到 ABS_TIME
    • 若目标设备或当前媒体不支持 seek,通常会以 90010005 返回控制失败。

音量控制

dlnaSetVolume({ volume })

  • 作用:向当前已选择设备发送音量设置指令。
  • 必填:volume,范围 0-100,插件会四舍五入为整数发送。
  • 成功返回:{ volume: number }
  • 说明:依赖目标设备实现 RenderingControl#SetVolume;部分设备可能只支持读取音量,不支持设置。

dlnaSetMute({ muted })

  • 作用:向当前已选择设备发送静音或取消静音指令。
  • 必填:muted
  • 成功返回:{ muted: boolean }
  • 说明:依赖目标设备实现 RenderingControl#SetMute

接收端监听

dlnaStartReceiverMonitor({ intervalMs?, idleIntervalMs?, includeVolume?, preferSubscribe? })

  • 作用:开始监听当前接收端的连接状态、播放状态、进度与音量信息。
  • 默认值:
    • intervalMs = 1000
    • idleIntervalMs = 3000
    • includeVolume = false
    • preferSubscribe = false
  • 成功返回:{ monitoring: true, mode: 'poll' | 'subscribe' }
  • 前置条件:必须已调用 dlnaSelectDevice

说明:

  • preferSubscribe: true 时,Android / iOS 会优先尝试订阅式状态更新;若设备不支持订阅,或订阅失效,会自动回退到轮询兜底。
  • success 返回的 mode 是启动时的当前模式;运行过程中实际模式可能在 pollsubscribe 之间切换,最终以 onDlnaReceiverEventdlnaGetReceiverStatus 返回的 status.mode 为准。

dlnaStopReceiverMonitor(options)

  • 作用:停止监听。
  • 成功返回:{ monitoring: false }

dlnaGetReceiverStatus(options)

  • 作用:获取最近一次接收端状态;若当前没有缓存状态,会主动请求一次设备状态。
  • 成功返回:DlnaReceiverStatus
  • 前置条件:必须已调用 dlnaSelectDevice

onDlnaReceiverEvent(callback)

  • 作用:监听接收端状态事件。
  • 返回值:listenerId

事件类型:

  • STATUS_CHANGED
  • PROGRESS
  • SEEKED
  • DISCONNECTED
  • RECONNECTED
  • ENDED

补充说明:

  • SEEKED 表示接收端进度发生明显跳变,适合响应拖动进度条等场景。
  • ENDED 为 best-effort 判定,依赖设备返回的状态和进度信息,不保证所有 DLNA 设备行为完全一致。
  • includeVolumefalseDlnaReceiverStatus.volumeDlnaReceiverStatus.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  // 平台不支持
  | 90010009  // 权限不足
  | 90010010  // 未连接 Wi-Fi

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 DlnaWifiInfo = {
  connected: boolean
  ssid?: string
  bssid?: string
  ipAddress?: string
  gateway?: string
  rssi?: number
  signalStrength?: number
  frequencyMHz?: number
  linkSpeedMbps?: number
}

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 DlnaResumeOptions = {
  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 DlnaSeekOptions = {
  positionSec: number
  success?: (res: UTSJSONObject) => void
  fail?: (res: DlnaFail) => void
  complete?: (res: any) => void
}

type DlnaSetVolumeOptions = {
  volume: number
  success?: (res: UTSJSONObject) => void
  fail?: (res: DlnaFail) => void
  complete?: (res: any) => void
}

type DlnaSetMuteOptions = {
  muted: boolean
  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 DlnaResume = (options: DlnaResumeOptions) => void
type DlnaPause = (options: DlnaPauseOptions) => void
type DlnaStop = (options: DlnaStopOptions) => void
type DlnaSeek = (options: DlnaSeekOptions) => void
type DlnaSetVolume = (options: DlnaSetVolumeOptions) => void
type DlnaSetMute = (options: DlnaSetMuteOptions) => 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.INTERNET
  • android.permission.ACCESS_NETWORK_STATE
  • android.permission.ACCESS_WIFI_STATE
  • android.permission.CHANGE_WIFI_MULTICAST_STATE

iOS

插件已在 utssdk/app-ios/Info.plist 中声明:

  • NSLocalNetworkUsageDescription
  • utssdk/app-ios/UTS.entitlements 中的 com.apple.developer.networking.multicast

说明:

  • 首次访问本地网络时,系统会弹出局域网权限提示。
  • DLNA 设备发现依赖 SSDP 组播,iOS 侧需要同时开启 multicast capability。
  • 若真机仍无法发送 M-SEARCH,请检查当前签名 / provisioning profile 是否允许该 capability。
  • 如果宿主应用需要自定义提示文案,可以在宿主工程侧覆盖该配置。

隐私声明

插件仅在局域网内发现 DLNA 设备并发送媒体投放控制指令,不采集或上传个人隐私数据。

调试建议

  • 推荐先开启插件日志:setDlnaLogEnabled(true)
  • 播放目标优先选择电视等真实 DLNA 接收端设备
  • 接入接收端监听时,建议在业务页面同时展示连接状态、播放状态、进度和音量信息,便于定位设备兼容性问题

隐私、权限声明

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

Android: INTERNET、ACCESS_NETWORK_STATE、ACCESS_WIFI_STATE、CHANGE_WIFI_MULTICAST_STATE、ACCESS_FINE_LOCATION(读取当前 Wi-Fi 名称需要);iOS: Local Network、Access Wi-Fi Information、定位使用说明(用于局域网设备发现、投放控制与当前 Wi-Fi 信息读取)

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

插件仅在局域网内发现 DLNA 设备、发送媒体投放控制指令,并可读取当前连接 Wi-Fi 信息用于局域网排查;不采集或上传个人隐私数据。

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