更新记录
1.0.1(2026-11-13)
- 增加设置倍速、设置起播位置
- 增加获取接收端能力接口
- 增加支持歌词、字幕接口
1.0.0(2026-11-13)
- 支持视频、图片、文件等,主要取决于接收端支持的格式
- 支持本地视频图片文件和远程视频图片文件
- 支持暂停、继续、停止、快进、音量(静音或具体某个音量)
- 支持接收端投屏信息(包含媒体信息、设备能力、播放进度、音量大小、静音状态)
- 支持苹果、Android、鸿蒙
- 支持vue2、vue3、uniapp、uniapp x(包含蒸汽模式)
平台兼容性
uni-app(3.8.11)
| Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
Android |
iOS |
鸿蒙 |
| √ |
√ |
- |
- |
√ |
√ |
5.0 |
√ |
√ |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| - |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
uni-app x(3.8.11)
| Chrome |
Safari |
Android |
Android插件版本 |
iOS |
iOS插件版本 |
鸿蒙 |
鸿蒙插件版本 |
微信小程序 |
| - |
- |
5.0 |
1.0.1 |
√ |
1.0.1 |
√ |
1.0.1 |
- |
其他
| 多语言 |
暗黑模式 |
宽屏模式 |
蒸汽模式 |
| √ |
√ |
√ |
√ |
dlna投屏,本地和远程视频图片文件投屏
功能介绍
- 支持视频、图片、文件等,主要取决于接收端支持的格式
- 支持本地视频图片文件和远程视频图片文件
- 支持暂停、继续、停止、快进、音量(静音或具体某个音量)、倍速、起播位置
- 支持获取接收端投屏信息(包含媒体信息、设备能力、播放进度、音量大小、静音状态)、获取接收端支持能力
- 支持歌词字母
- 支持苹果、Android、鸿蒙
- 支持vue2、vue3、uniapp、uniapp x(包含蒸汽模式)
集成步骤
- 插件详细集成步骤可以参考https://www.jianshu.com/p/c1615a7880a7
- 拷贝demo里的nativeResources、AndroidManifest.xml、harmony-configs、Info.plist文件到项目根目录
- iOS打包的provisioining profile文件需要开通组播/广播权限,参考https://www.jianshu.com/p/c2f6e6aa41a3?v=1682211826634,单播可以不用
- 如需定制或增加功能请点击插件标题下方的"进入交流群"私聊作者
接口
import * as sp from "@/uni_modules/wrs-uts-scrproj"
sp.onSetCallback((resp) => {
// console.log("onSetCallback resp:" + JSON.stringify(resp))
const opt = resp.opt
switch (opt) {
// 扫描到设备
case "onDeviceFound": {
// console.log("device:" + JSON.stringify(resp.device))
this.upsertDevice(resp.device)
}
break
// 设备断开连接
case "onDeviceDelete": {
this.removeDevice(resp.device.address, resp.device.location)
}
break
default:
break
}
})
sp.startSearch()
sp.stopSearch()
let url = "" // 视频/图片/文件的地址,可以是本地或远程地址,如远程地址https://www.baxxx/aa.mp4、本地地址/ss/xx/xx/aa/mp4
let location = "" // 从扫描到的设备里获取
const start = 0 // 从第几秒开始播放
const speed = '1' // 播放速度,需要接收端支持
const tracks = this.buildTextTracks()
const preferredId = tracks != null ? tracks[0].id : null
const media = {
url: url,
mediaKind: this.mediaKind,
mimeType: null,
title: null,
textTracks: tracks,
preferredTextTrackId: preferredId,
textTrackFailurePolicy: this.textTrackFailurePolicy
}
const startText = start > 0 ? `,从 ${this.formatTime(start)} 开始` : ''
const speedText = this.supportsSpeed ? `,${speed}x` : ''
const trackText = tracks != null ? `,${tracks.length} 个字幕轨` : ''
const kindText = this.mediaKind === 'auto' ? '' : `,类型 ${this.mediaKind}`
this.pushLog(`投屏请求: ${this.mediaUrl}${kindText}${startText}${speedText}${trackText}`)
sp.startCast(location, media, start, speed, (ok, msg) => {
this.casting = false
if (ok) {
this.pushLog(msg && msg !== 'ok' ? msg : '投屏成功')
uni.showToast({
title: '投屏成功',
icon: 'success'
})
setTimeout(() => {
this.refreshTransportState()
this.refreshPosition()
this.refreshMediaInfo()
}, 500)
} else {
let errText = msg
try {
const err = JSON.parse(msg)
errText = `[${err.errCode}] ${err.errMsg}`
} catch (e) { }
this.pushLog(`投屏失败: ${errText}`)
uni.showToast({
title: errText || '投屏失败',
icon: 'none'
})
}
})
sp.pause(location, (ok, msg) => {
if (ok) {
this.pushLog('暂停请求已发送')
setTimeout(() => this.refreshTransportState(), 500)
} else {
this.pushLog(`暂停失败: ${msg}`)
}
})
sp.resume(location, (ok, msg) => {
if (ok) {
this.pushLog('继续请求已发送')
setTimeout(() => this.refreshTransportState(), 500)
} else {
this.pushLog(`继续失败: ${msg}`)
}
})
sp.stop(location, (ok, msg) => {
if (ok) {
this.pushLog('停止请求已发送')
this.stopProgressTimer()
this.currentSec = 0
setTimeout(() => this.refreshTransportState(), 500)
} else {
this.pushLog(`停止失败: ${msg}`)
}
})
sp.getTransportInfo(location, (state, msg) => {
this.transportState = state
this.playing = state === 'PLAYING'
this.paused = state === 'PAUSED_PLAYBACK'
if (state === 'PLAYING') {
this.startProgressTimer()
} else {
this.stopProgressTimer()
}
if (state === 'UNKNOWN') this.pushLog(`查询状态失败: ${msg}`)
// 状态变化后同步动作能力
this.refreshActions()
})
sp.getCurrentTransportActions(location, (actions, msg) => {
if (actions && actions.length > 0) {
this.actions = actions
}
})
sp.getMediaInfo(location, (meta, msg) => {
if (meta == null) return
this.mediaTitle = meta.title || ''
this.mediaProtocol = meta.protocolInfo || ''
})
sp.getPositionInfo(location, (info, msg) => {
if (info == null) return
this.durationSec = info.durationSec
this.currentSec = info.currentSec
})
sp.seek(location, target, (ok, msg) => {
this.seeking = false
if (!ok) this.pushLog(`跳转失败: ${msg}`)
setTimeout(() => this.refreshPosition(), 300)
})
sp.getVolume(location, (v, msg) => {
if (v >= 0) this.volume = v
})
sp.setVolume(location, v, (ok, msg) => {
this.volumeChanging = false
if (!ok) this.pushLog(`设置音量失败: ${msg}`)
})
sp.getMute(location, (mute, msg) => {
this.muted = mute
})
const target = !this.muted
sp.setMute(location, target, (ok, msg) => {
if (ok) {
this.muted = target
this.pushLog(target ? '已静音' : '已取消静音')
} else {
this.pushLog(`静音切换失败: ${msg}`)
}
})
sp.getDeviceCapabilities(location, (caps, msg) => {
if (caps == null) {
this.pushLog(`能力探测失败: ${msg}`)
this.deviceCaps = null
return
}
this.deviceCaps = caps
const speedText = caps.supportsSpeed ? `支持倍速(${caps.speeds.join('/')})` : '不支持倍速'
const seekText = caps.supportsSeek ? '支持定位' : '不支持定位'
this.pushLog(`设备能力: ${speedText},${seekText}`)
})
let speed = "1"
sp.setPlaySpeed(location, speed, (ok, msg) => {
if (ok) {
this.currentSpeed = speed
this.pushLog(`倍速已切换为 ${speed}x`)
setTimeout(() => this.refreshTransportState(), 300)
} else {
this.pushLog(`倍速切换失败: ${msg}`)
uni.showToast({
title: msg || '设备不支持该倍速',
icon: 'none'
})
}
})