更新记录
1.1.0(2022-12-22)
- 修复自第二次起投屏播放,没有进度回调的bug。
1.0.1(2022-12-19)
- 提供了包含 UI 界面的 Demo 源码,如果您有相关需求可直接使用;Demo 包含本地或网络视频、音频、图片投屏、本地HttpServer服务器使用、选择本地文件等常见功能,以及 App 端的投屏控制(遥控器)等业务场景,详见 体验 Demo。
- 具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目)
- 当前只有Android版,iOS版待定!
- 首次接入使用时,我们建议你下载插件示例项目过一遍再按需对接到你的项目上,这样可少走许多弯路也避免不会用或用法不对,就怪这怪那的等一系列吐槽。
- 熊猫投屏帮助文档:https://www.yuque.com/uqdthn/ntfml4/ggkzo6kgrb9ihnm5?singleDoc#《uniapp插件市场-熊猫DLNA投屏-产品概述与使用手册》
平台兼容性
Android | Android CPU类型 | iOS |
---|---|---|
适用版本区间:5.1 - 12.0 | armeabi-v7a:支持,arm64-v8a:支持,x86:未测试 | × |
原生插件通用使用流程:
- 购买插件,选择该插件绑定的项目。
- 在HBuilderX里找到项目,在manifest的app原生插件配置中勾选模块,如需要填写参数则参考插件作者的文档添加。
- 根据插件作者的提供的文档开发代码,在代码中引用插件,调用插件功能。
- 打包自定义基座,选择插件,得到自定义基座,然后运行时选择自定义基座,进行log输出测试。
- 开发完毕后正式云打包
付费原生插件目前不支持离线打包。
Android 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/android
iOS 离线打包原生插件另见文档 https://nativesupport.dcloud.net.cn/NativePlugin/offline_package/ios
注意事项:使用HBuilderX2.7.14以下版本,如果同一插件且同一appid下购买并绑定了多个包名,提交云打包界面提示包名绑定不一致时,需要在HBuilderX项目中manifest.json->“App原生插件配置”->”云端插件“列表中删除该插件重新选择
熊猫DLNA投屏-产品概述与使用手册
在线帮助文档:https://www.yuque.com/uqdthn/ntfml4/ggkzo6kgrb9ihnm5?singleDoc#《uniapp插件市场-熊猫DLNA投屏-产品概述与使用手册》
2022/12/12 更新:已有功能
一、优势
投屏:支持本地文件或网络文件投屏
- 支持本地或网络m3u8流投屏
- 支持本地或网络rtmp流投屏(如何测试本地rtmp投屏?用两个手机下载腾讯云视立方app,打开直播推流-V2,两个手机配合连接上后,把拉流地址用投屏demo投屏到电视即可,a手机推流、b手机拉流、c电视拉流,b和c都可以看到a推流画面,注:需在同一WiFi环境下)
- 支持本地或网络视频投屏
- 支持本地或网络音频投屏
- 支持本地或网络图片投屏
场景化Demo:
- 提供了包含 UI 界面的 Demo 源码,如果您有相关需求可直接使用;Demo 包含本地或网络视频、音频、图片投屏、本地HttpServer服务器使用、选择本地文件等常见功能,以及 App 端的投屏控制(遥控器)等业务场景,详见 体验 Demo。
- 具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目)
二、扩展
扩展:支持列表播放
- 支持连续播放一个视频列表
- 支持连续播放一个音频列表
- 支持连续播放一个图片列表
三、特性
特性:基于Cling库封装的DLNA投屏插件,支持DMC、DMS、DMR功能。
- 支持移动端控制(DMC)功能
- 支持投射本地文件(DMS)或网络文件
- 支持电视端(接收端)设备播放器功能(DMR)
四、发现设备
发现设备
- 发现设备
- 发现设备监听
五、事件监听
事件监听
-
发现设备监听 :::tips 当发现设备加入或退出当前同一WiFi网络后,会实时更新并回调给uniapp。 回调信息:设备列表<搜索到的所有设备> 方便你动态更新现有设备列表。 :::
-
设备状态监听 :::tips 当手机(发送端)与电视(接收端)连接时后,会实时更新并回调给uniapp。 回调信息:设备状态<传输(即投屏中)、播放、暂停、停止> :::
-
播放器进度监听 :::tips 说明同上 回调信息:当前进度(3)、总时长(230)、当前进度转time时间格式(00:03),总时长转time时间格式(03:50) :::
-
从本地选择文件监听(额外功能) :::tips 当用户从本地相册中选择视频、音频、图片后,会实时更新并回调给uniapp。 回调信息:选择的文件路径 :::
-
权限申请监听(额外功能) :::tips 监听用户是否同意某权限,具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目) :::
六、操作功能
操作功能
- 初始化Cling-DLNA服务(启动服务)
- 停止Cling-DLNA服务(停止服务)
- 搜索设备(手动/主动搜索)
- 绑定设备(选择绑定一个设备后用于投屏到此设备)
- 开始投屏
- 播放
- 暂停
- 停止
- seek(改变投屏进度)
- 设置音量(音量调节)
- 获取音量
- 设置是否静音
- 获取是否静音
- 获取WiFi信息(额外功能)
- 申请定位权限(额外功能)[注:获取WiFi信息需要此权限,安卓8开始]
- 从本地选择图片(额外功能)
- 从本地选择视频(额外功能)
- 从本地选择音频(额外功能)
- 开启本地HTTPServer服务器(额外功能)
- 停止本地HTTPServer服务器(额外功能)
- 获取本机(当前手机)的默认储存位置(额外功能)
- 本地文件路径转本地htpp服务器文件路径(额外功能) :::tips 可用于video播放本地文件或cling投屏本地文件到电视等 :::
七、API使用手册
1. 引用插件
// 导入 "熊猫投屏" 原生插件
var XMDlnaCling = uni.requireNativePlugin('XM-TP-Dlna-Cling');// 熊猫DLNA-Cling投屏
// 导入 "熊猫本地服务器" 原生插件
var XMHttpServer = uni.requireNativePlugin('XM-TP-Http-Server');// 熊猫Http-Server本地服务器
// 导入 "globalEvent" uniapp官方app内置原生插件
var globalEvent = uni.requireNativePlugin('globalEvent');// 动态回调监听插件(重要!用于投屏插件->发现设备\控制设备\AV传输\等回调信息)
2. 添加事件监听
- 发现设备监听
// 添加事件侦听器 可放在 mounted、created、onLoad等生命周期里挂载,不可挂载在 export default 的上面
// 添加监听 "找到当前局域网搜索到的所有设备列表回调" -> 注意,下面的onClingDevices、onPlayProgress、onState回调方法在你initCling事件后就自动启动监听了
globalEvent.addEventListener('onClingDevices', (res) => { // 找到的当前搜索到的所有设备回调,回调会来回的显示当前找到的设备,直到你销毁Cling服务后停止。也可以手动查找设备,调用startSearch事件,用于手动搜索,通常是不需要此方法的
// 此方法是动态的,用于当前局域网里的某个电视机盒已退出当前网络,则自动更新状态,把退出当前同一网络的电视机盒从设备<列表>里删除,反之为某电视机盒进入当前同一网络,则吧这个电视机盒添加进来。
console.log('onClingDevices', JSON.stringify(res));
let reslout = res.detail
uni.showModal({
title: '设备列表',
content: JSON.stringify(res.detail),
showCancel: false
});
});
- 设备状态监听
// 添加监听 "投屏状态回调" -> 传输、播放、暂停、停止
globalEvent.addEventListener('onState', (res) => { // 投屏状态回调 传输、播放、暂停、停止
console.log('onState', JSON.stringify(res));
let reslout = res.detail
});
- 播放器进度监听
// 添加监听 "播放进度回调监听" -> 当前播放进度(int 整数型)、视频总长(int 整数型)、当前播放进度(time 00:01 时间格式)、视频总长(time 02:10:00 时间格式)
globalEvent.addEventListener('onPlayProgress', (res) => { // 播放进度回调监听
console.log('onPlayProgress', JSON.stringify(res));
let reslout = res.detail
// current int 当前进度
// duration int 总时长
// current_toTime time 当前进度
// duration_toTime time 总时长
let { Devent } = this;
// 判断当前状态是否停止 "state":"STOPPED" ,并且当前播放进度是否等于视频总长 current == duration,如果是,则调用停止方法,不然回调一直在后台进行。如果是多集视频的,可在视频播放完毕后调用停止后重新投屏下一集。
if (Devent && Devent['state'] == "STOPPED" && Devent['current'] == Devent['duration']) { // 当前视频播放完毕了
// 1. 停止投屏的视频,不是停止Dlan-Cling服务哦!
// 2.如果是多集视频的,即视频列表播放,可再此处接着写开始投屏方法,吧下一集参数传递给投屏方法即可
// 2.如果是单集循环播放,可在此处使用开始投屏方法
}
});
- 从本地选择文件监听(额外功能)
// 添加监听 "选择本地文件回调" -> 本地文件路径
globalEvent.addEventListener('onActivityResult', async(ret) => {
console.log('onActivityResult' + JSON.stringify(ret));
if (ret.return_result_type == 'CODE_REQUEST_MEDIA') { // 代码_请求_媒体
// ret.tempFilePaths == 选择的本地文件的路径
_that.LocalFilePath = ret.tempFilePaths;
}
});
- 权限申请监听(额外功能)
// 添加监听 "请求权限结果回调" -> 允许 ? 拒绝/禁止 ? 拒绝/禁止且不再访问
globalEvent.addEventListener('onRequestPermissionsResult', (ret) => {
console.log('onRequestPermissionsResult' + JSON.stringify(ret));
/**
* 状态码说明
* int 405 = 请求权限申请时被禁止,可能是用户主动拒绝过,也可能是安卓系统默认禁止的。(用户拒绝过这个权限了,应该提示用户,为什么需要这个权限)
* int 203 = 未授权,请求权限时被用户主动点击了“拒绝”授权 或 未拥有相应权限。
* int 202 = 已授权,请求权限时被用户主动点击了“同意”授权。
* int 200 = 已授权,已拥有此权限。
*/
// 判断代码请求类型 ? 获取wifi需定位权限 | 获取本地文件需读取储存权限
if (ret.return_result_type == "CODE_REQUEST_LOCATION") { // 代码_请求_位置(定位)
// 如需详细代码示例,可下载插件示例项目查看,pages/index/index-psd.vue
// 具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目)
return;
}
if (ret.return_result_type == "CODE_REQUEST_MEDIA") { // 代码_请求_媒体
if (ret.code == 203) { // 权限申请失败
} else if (ret.code == 202) { // 权限申请成功
// 权限申请成功后再次调用选择本地音频方法即可
} else if (ret.code == 405) { // 请求权限申请时被禁止
}
return;
}
});
3. 初始化Cling-DLNA服务(启动服务)
// 初始化并启动Cling-DLNA服务
XMDlnaCling.initCling((res) => {
console.log(res);
uni.showModal({
title: '返回数据' + res.code,
content: JSON.stringify(res),
showCancel: false
});
});
4. 停止Cling-DLNA服务(停止服务)
// 销毁Service远程服务进程 | 停止Cling服务
XMDlnaCling.stopClingService();
5. 搜索设备(手动/主动搜索)
// 开始搜索设备(手动搜索,globalEvent会自动监听回调)
XMDlnaCling.startSearch((res) => {
console.log(res);
uni.showModal({
title: '返回数据',
content: JSON.stringify(res),
showCancel: false
});
})
6. 绑定设备(选择绑定一个设备后用于投屏到此设备)
// 获取设备索引
let { index } = event;
// 选择一个设备并绑定
XMDlnaCling.SetBindingCurrClingDevice({
index: index,// 投屏设备索引 | <设备列表数组下标>
}, (res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
uni.showToast({
title: res.message,
position: 'bottom',
icon: "none"
});
});
7. 开始投屏
// 开始投屏播放 | 开始投射播放
XMDlnaCling.StartCastingPlayback({
title: this.fileName,// 视频名称 | String类型 | 必填 | title可为空,但title字段不存在会报错
id: "45678",// 视频id | String类型 | 必填 | id可为空,但id字段不存在会报错
url: this.tpurl // 视频url地址 | String类型 | 必填,不可为空
}, (res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
uni.showToast({
title: res.message,
position: 'bottom',
icon: "none"
});
});
8. 播放
// 播放
XMDlnaCling.play((res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
});
9. 暂停
// 暂停
XMDlnaCling.pause((res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
});
10. 停止
// 停止
XMDlnaCling.stop((res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
});
11. seek(改变投屏进度)
// 改变投屏进度
let { progress } = event;
XMDlnaCling.seek({
progress: Number(progress)// 单位s(秒)
}, (res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
});
12. 设置音量(音量调节)
let { Volume } = event;
// 设置音量
XMDlnaCling.setVolume({
volume: Number(Volume)// 音量值 0 至 100,0无声音,100最大音量
}, (res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
if (res.state == 'success') {
// 记录音量值
_that.Devent['Volume'] = Volume;
}
});
13. 获取音量
// 获取音量
XMDlnaCling.getVolume((res) => { // 注意:第一次投屏播放的时候,大部分电视正常,只有少部分电视获取的音量始终为100,这大概是接收端<电视盒子>没有回调电视当前音量,所以调用方法始终返回100!需要你手动设置一次音量后再获取音量,接收端<电视>才能正常返回当前电视音量。后期再看看怎样处理,吧这个问题解决掉。
console.log(JSON.stringify(res));
// res.state == success 成功
});
14. 设置是否静音
// 静音开关
XMDlnaCling.muteSwitch({
isMute: true,// 是否静音 Boolean true=静音 false=声音
}, (res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
});
15. 获取是否静音
// 获取是否静音
XMDlnaCling.getMute((res) => {
console.log(JSON.stringify(res));
// res.state == success 成功 | res.state == fail 失败
});
16. 获取WiFi信息(额外功能)
// 获取当前连接的wifi名称
XMDlnaCling.getConnectWifiInfo((ret) => {
// 在调用getConnectWifiInfo方法时,会自动判断是否拥有定位权限,当没有权限时,会判断是否在这之前被拒绝过,如果没有被拒绝过,则自动申请,如果有拒绝过,则需要重新申请,这时候需要在申请前告诉他为什么要这个权限,然后经过用户同意才可以,所以,当判断到被拒绝过时,直接走405状态码,这时候要做的时,你可以在页面自己处理,但如果用户直接永久禁止(有的手机叫-禁止且不再询问)后,检查到没有权限时,无法弹出权限申请,也会导致获取信息失败,切记!
// 这个方法回调里必须用合并对象,用JSON.Stringify或JSON.parse是可以拿到对象,但能打印对象,无法打印对象字段,很是奇葩的
//var ceshi = Object.assign({}, ret);
//var ceshi2 = {...{}, ...ret}
// 用空对象{}和ret合并
var res = {...{}, ...ret};
// 判断是否获取成功,获取失败时,说明没有权限,我们需要主动向用户申请权限
if (res['code'] == 200) { // 拥有定位权限,获取wifi信息成功,注:安卓8开始,必须要定位权限才能获取wifi信息
uni.showModal({
title: '返回数据' + res.code,
content: 'wifi名称:' + JSON.stringify(res.ssid),
showCancel: false
});
} else
if (res['code'] == 405) {
uni.showModal({
title: "权限申请",
content: "在获取wifi信息前 需要用户授权 定位权限,Android 8 以后 要 获取wifi信息 是需要【获取位置信息】权限 才可以正常使用的",
confirmText: "同意申请授权",
success: (result) = >{
if (result.confirm) {
console.log('用户点击确定');
// 直接调用申请定位权限方法
} else if (result.cancel) {
console.log('用户点击取消');
uni.showToast({
title: "获取wifi信息失败"
});
}
}
});
}
});
17. 申请定位权限(额外功能)[注:获取WiFi信息需要此权限,安卓8开始]
// 申请定位权限
XMDlnaCling.RequestPermissionLOCATION();
18. 从本地选择图片(额外功能)
// 选择本地图片,插件内置的有,uniapp自带也有,可根据你的项目需求选择使用
// 打开图片文件选择器
XMDlnaCling.selectImage(); // 选择的文件地址在上面的 -> 走异步回调方法体 -> mounted生命周期里onActivityResult的监听事件回调
// 有点懵?我们已经为你写好了所有功能示例,如需具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目)
19. 从本地选择视频(额外功能)
// 选择本地视频,插件内置的有,uniapp自带也有,可根据你的项目需求选择使用
// 打开视频文件选择器
XMDlnaCling.selectVideo();// 选择的文件地址在上面的 -> 走异步回调方法体 -> mounted生命周期里onActivityResult的监听事件回调
// 有点懵?我们已经为你写好了所有功能示例,如需具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目)
20.从本地选择音频(额外功能)
// 选择本地音频,插件内置的有,但uniapp没有哦!所以选择本地音频只能用插件内置的
// 打开音频文件选择器
XMDlnaCling.selectAudio();// 选择的文件地址在上面的 -> 走异步回调方法体 -> mounted生命周期里onActivityResult的监听事件回调
// 有点懵?我们已经为你写好了所有功能示例,如需具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目)
21. 开启本地HTTPServer服务器(额外功能)
// 开启本地服务器 | 初始化本地httpServer
XMHttpServer.initHttpServer({
isCustomRoot: false,// 是否自定义 http 服务器根目录,false时会自动使用你的手机默然储存根目录作为本地服务器根目录使用
HttpServerRootDirectory: "",// 作为http服务器根目录的路径,如果isCustomRoot为true, 则必填!否则为空即可(如果你的app内下载缓存的文件是放在uniapp内置的沙盒目录如doc等,你需要播放app内缓存的视频或音频啥的,你需要把uniapp的沙盒路径取出来,然后给我这个参数即可,此时你的uniapp沙盒目录就是本地服务器根目录了)
}, (res) => {
uni.showModal({
title: "回调信息",
content: JSON.stringify(res),
showCancel: false
});
});
22. 停止本地HTTPServer服务器(额外功能)
// 关闭本地服务器 | 销毁本地httpServer服务
XMHttpServer.destroyHttpServer();
23. 获取本机(当前手机)的默认储存位置(额外功能)
// 获取手机默然存储根目录的地址
// 此方法XM-Http-Server和XM-Dlna-Cling两个插件都有集成
XMHttpServer.getExternalStorageDirectory((res) = >{
// 本机储存目录根路径
this.localMediaRootPath = res.localMediaRootPath;
});
24. 本地文件路径转本地htpp服务器文件路径(额外功能)
// 本地文件路径转本地htpp服务器文件路径
return new Promise((resolve, reject) => {
// 本地文件路径(用于从系统相册里选择图片、视频、音频)
let { LocalFilePath } = this; // uniapp选择文件后,返回的地址里前面加的有 file:// ,我们需要先把他删除
// 本机储存目录根路径
let { localMediaRootPath } = this;
let newLocalFilePath = "";
// 判断是否uniapp的api调用的选择文件
if (LocalFilePath.includes("file://")) { // 文件路径包含file:// 就是uniapp的api选择文件返回的地址
newLocalFilePath = LocalFilePath.replace("file://", ""); // 把 file:// 替换为 空的,就是真实的本地文件路径了
} else {
newLocalFilePath = LocalFilePath;
}
// 文件路径
this.newLocalFilePath = newLocalFilePath;
// 此方法XM-Http-Server和XM-Dlna-Cling两个插件都有集成
XMHttpServer.tryTransformLocalMediaAddressToLocalHttpServerAddress({ // 异步方法,所以外层要加Promise,或者你在这个方法回调里写return true,然后其他方法里调用方法的时候用判断,示例:let isok = 这个方法名() if (isok == true) this.开始投屏事件
url: newLocalFilePath,// 你的本地文件路径
}, (res) => {
let reslut = {
HttpServerFilePath: res.HttpServerFilePath,// 转换好的本地服务器文件路径
//fileName: _that.getFileName() // 获取文件的名字
}
resolve(reslut);
});
});
// 有点懵?我们已经为你写好了所有功能示例,如需具体代码和说明请下载本插件示例项目查看(插件主页>右上方>蓝色按钮>使用HBuilder X导入示例项目)
2023-0111-2220补充已测试第三方电视端软件投屏播放器
八、熊猫投屏-投第三方电视端软件
-
解释说明:也叫利用第三方电视端软件自带的投屏服务投屏到电视,这时采用的是第三方软件的播放器投屏播放,这种情况一般适用于电视自带的投屏服务播放器不支持某些格式时才需要用第三方软件来投屏,如电视自带投屏服务播放器可能不支持m3u8流、rtmp流、rtsp流视频等。
-
重要说明:电视打开第三方电视端软件时(如奇异果TV),需要等待3-15秒左右才可以搜索到软件自带的投屏服务。
九、第三方电视端软件测试结果如下
乐播投屏:
- 支持本地或网络视频(已测试格式:.flv、.3gp、.m3u8、.rtsp、.mp4)
- 支持本地或网络音频(已测试格式:.mp3、.flac)
- 不支持本地图片或网络图片
奇异果TV:爱奇艺平台电视端软件,又名为银河奇异果
- 支持本地或网络视频(已测试格式:.flv、.m3u8、.rtsp、.mp4、.3gp)
- 不支持本地或网络音频
- 不支持本地或网络图片
芒果TV:芒果TV平台电视端软件
- 支持本地或网络视频(已测试格式:.m3u8、.rtsp、.flv、.mp4、.rtmp、.3gp)
- 支持本地或网络音频(已测试格式:.mp3、.flac)
- 不支持本地或网络图片
CIBN酷喵:优酷视频平台电视端软件
- 支持本地或网络视频(已测试格式:.rtmp不支持、.flv、.m3u8、.rtsp、.mp4)
- 支持本地或网络音频(已测试格式:.flac、.mp3)
NewTV极光:腾讯视频平台电视端软件
- 搜索不到腾讯的投屏服务
- 注:查了一下,腾讯投屏基于CyberGarage开发,而熊猫投屏基于Cling,两者之间各不存在相同或兼容性。