1.效果图
手机控制端
电脑受控端
电脑需要安装【完美解码】这款播放软件,使用nodejs作为后端服务接收手机发送的指令,再模拟键盘输入快捷键控制播放软件。
2. nodejs 代码
此处安装 robotjs 库会碰问题
解决方法:
运行 npm install --global --production windows-build-tools
进入以下目录手动安装 python2.7 ,然后配置环境变量,重启电脑,再次运行 npm install robotjs --save
C:\Users\Administrator.windows-build-tools\python27
var exec = require('child_process').exec;
var robot = require("robotjs");
var fs = require('fs');
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
server.on('close',()=>{
console.log('socket已关闭');
});
server.on('error',(err)=>{
console.log(err);
});
server.on('listening',()=>{
console.log('socket UDP 正在监听中...');
server.setBroadcast(true);//开启广播
server.setTTL(128);//路由一跳TTL减一,减到零抛弃数据包
//server.send('hello i m server',8061,'192.168.1.255');
});
//通过message事件接收数据
server.on('message',(msg,rinfo)=>{
//**读取影片名称**
var path = 'd://video//';
var filesList = [];
readFileList(path, filesList);
console.log(`receive message from ${rinfo.address}:${rinfo.port}`); //客户端ip和port
//var newtopic = new Buffer.from(msg,"utf-8").toString();//将缓存的消息转换成string
var newstr = msg.toString('hex', 0, msg.length); //使用16进制表示的字符串
console.log(newstr)
var str = newstr.substr(0,2) //截取前面两个字符
console.log(str)
if (str == '01'){ //播放
let strNo = newstr.substr(3,2)
let orderNumber = parseInt(strNo,16) //16进制转为10进制数字
console.log( orderNumber )
let orderName = filesList[orderNumber].toString() //根据下标选择对应的影片
let playPath = "d:\\video\\" + orderName //拼接成绝对路径
exec('explorer.exe ' + playPath); //使用默认程序打开视频文件,此处是PotPlayer
}
else if(str == '08'){
robot.keyTap("backspace"); //模拟键盘输入退格键,重新播放
}
else if(str == '20'){
robot.keyTap("space"); //模拟键盘输入空格键,暂停&播放
}
else if(str == '7d'){
robot.keyTap("enter"); //模拟键盘输入回车,全屏播放
}
else if(str == '02'){
//robot.keyTap("escape"); //退出
robot.keyToggle("f4","down","alt"); //按下alt+f4 退出
robot.keyToggle("f4","up","alt"); //释放alt+f4,否则alt键一直是按下状态
}
else if(str == '7f'){ //刷新影片
// var path = 'd://video//';
// var filesList = [];
// readFileList(path, filesList);
let fileName = filesList.toString(); //把数组转换为字符串
// 如果有多台电脑参与受控,选出一台做主受控,其余作为从受控,注释掉下面的语句,否则多台电脑返回相同的消息给客户端导致卡死
// 给客户端返回消息,第一个参数是返回给客户端的字符串,第二个参数是客户端的端口,第三个参数是广播地址
server.send(fileName,8061,'192.168.1.255');
}
});
// 读取文件夹里面的文件名
function readFileList(path, filesList) {
var files = fs.readdirSync(path);
files.forEach(function (itm, index) {
var stat = fs.statSync(path + itm);
if (stat.isDirectory()) {
//递归读取文件
readFileList(path + itm + "/", filesList)
} else {
var obj = {};//定义一个对象存放文件的路径和名字
//obj.path = path; //路径
//obj.filename = itm //名字
obj = itm //名字
filesList.push(obj);
}
})
};
//绑定本机端口和ip,要接收数据的话必须绑定
server.bind('8060','192.168.1.8');
3. uniapp 代码
由于用到安卓原生库,所以只能编译为安卓app来运行。
<!-- index.vue -->
<template>
<view class="page">
<picker @change="bindPickerChange" :value="index" :range="itemList">
<view class="uni-input"> 选片:{{itemList[index]}}</view>
</picker>
<view class="content">
<!-- <button type="default" @click="btnplay()">选片</button> -->
<button type="default" @click="btnreplay()">重播</button>
<button type="default" @click="btnpause()">暂停/播放</button>
<button type="default" @click="btnfull()">全屏</button>
<button type="default" @click="btnquit()">退出</button>
<button type="default" @click="getList()">刷新影片</button>
</view>
<view class="st">
<image class="stimg" src="../../static/st.png" mode="" @longpress="seting()"></image>
</view>
</view>
</template>
<script>
//声明全局变量
var ip = ''
export default {
data() {
return {
index: 0,
itemList:[]
}
},
onLoad() {
let that = this
let tempip = uni.getStorageSync('serverip') //从缓存读取
//缓存没有数据的时候跳转到配置页面
if (tempip ==''){
//console.log('空',tempip)
uni.navigateTo({
url:'../seting/seting'
})
} else {
console.log('不空',tempip)
ip = tempip
setTimeout(function() {
that.getList()
},2000)
}
},
methods: {
//设置参数
seting(){
uni.showModal({
title: '提示',
content: '是否确定要重置参数?',
success: function (res) {
if (res.confirm) {
//跳转到配置页面
uni.navigateTo({
url:'../setting/setting'
})
} else if (res.cancel) {
//console.log('取消')
}
}
})
},
//udp通讯
udptest(arrdata) {
//引入安卓原生库
var DatagramPacket = plus.android.importClass('java.net.DatagramPacket');
var DatagramSocket = plus.android.importClass('java.net.DatagramSocket');
var InetAddress = plus.android.importClass('java.net.InetAddress');
var NetworkInterface = plus.android.importClass('java.net.NetworkInterface');
var JString = plus.android.importClass('java.lang.String');
var socket;
var port = 8060; //广播端口,与服务端的端口一致
var getPort = 8061; //接收消息端口
var timeout = 6000; //超时时间
try {
if (DatagramSocket == undefined) { return }
// 创建广播地址
//var udpip = InetAddress.getByName("192.168.1.255");
var udpip = InetAddress.getByName(ip);
// 绑定本机接收UDP反馈消息的端口号
socket = new DatagramSocket(getPort);
// 设置接收超时时长
socket.setSoTimeout(timeout);
// 发送广播数据
//var sendData = Array.prototype.slice.call((new Buffer(`hello server`)), 0);
var sendData = arrdata
console.log(sendData)
var sendPacket = new DatagramPacket(sendData, sendData.length, udpip, port);
socket.send(sendPacket);
//console.log('广播地址:'+ip.getHostAddress(), '端口号:'+sendPacket.getPort())
// 接收数据
var isReceive = true;
while (isReceive) {
try {
// 设置接收缓存,需要用0填充,否则为 null 无法接收。
var buffer = new Array(1024).fill(0);
var packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
var data = new JString(packet.getData()).trim();
if (data.length == 0) {
// 接收超时,结束接收
isReceive = false;
} else {
console.log('=====收到数据======', data);
return data;
}
} catch (ex) {
socket.close();
isReceive = false;
console.log('接收数据失败')
}
}
} catch (ex) {
console.log('========出错了=======', ex);
} finally {
if (socket != undefined) {
socket.close();
}
}
},
//选择影片
bindPickerChange: function(e) {
//console.log('picker值为', e.target.value)
this.index = e.target.value
this.btnplay()
},
//获取影片名称
getList() {
var datastr = [127]; //控制指令参数
var tempstr = this.udptest(datastr); //调用函数并传入参数,获得返回值赋给 tempstr
//添加影片名称到数组
if(tempstr){
var ss = tempstr.split(","); // 在每个(,)处进行分解 ---字符串转数组
this.itemList = ss
}else{
console.log('返回数据异常!')
}
},
//播放
btnplay() {
var playindex = this.index;
var datastr = [1,playindex] ; //控制指令参数,数组里的第一个参数1代表播放,第二个参数代表第几个影片
console.log(datastr);
this.udptest(datastr);
},
//重播
btnreplay(){
var datastr = [8];
this.udptest(datastr);
},
//暂停
btnpause(){
var datastr = [32];
this.udptest(datastr);
},
//全屏
btnfull(){
var datastr = [125];
this.udptest(datastr);
},
//退出
btnquit(){
var datastr = [2];
this.udptest(datastr);
}
}
}
</script>
<style>
.page{
/* background-color: #cee9f8 */
}
.uni-input{
height: 80rpx;
line-height:90rpx;
font-size: 40rpx;
color: #DD524D;
margin-left: 26rpx;
}
.content {
display: flex;
flex-direction: column;
margin: 30rpx;
}
.st{
height: 80rpx;
width: 80rpx;
float: right;
margin-top: 200rpx;
margin-right: 20rpx;
/* border-style:solid; */
/* border-color: #DD524D; */
}
.stimg{
height: 100%;
width: 100%;
}
button {
margin: 10rpx;
}
</style>
网络配置页面代码
<!-- setting.vue -->
<template>
<view>
<view class="content">
<radio-group class="radiogroup" @change="radioChange">
<label class="radio"><radio value="r1" checked="true" />自动配置</label>
<label class="radio"><radio value="r2" />手动配置</label>
</radio-group>
<view class="inputip" v-if="isshow === 'r2'">
<view class="">请输入服务器IP:</view>
<input class="inputtext" type="text" @input="onKeyInput" placeholder="192.168.1.234" />
</view>
<button class="btn" type="default" @click="btnok()">确定</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
isshow:'r1',
ip:''
}
},
methods: {
radioChange: function(e) {
//console.log(e.detail.value)
this.isshow = e.detail.value
},
onKeyInput: function(event) {
this.ip = event.target.value
},
btnok(){
var that = this
if(this.isshow=='r1'){ //自动配置
this.getconfig()
}else{ //手动配置
console.log(this.ip)
if(this.ip.length <= 6){
uni.showToast({
title: "ip地址不正确",
icon:'none',
duration: 3000
});
}else{
uni.setStorageSync('serverip',this.ip)
uni.showToast({
title: "参数设置成功,请完全关闭软件再次运行!",
icon:'none',
duration: 5000
});
}
}
},
getconfig(){
//从web服务器读取配置文件并缓存到本地
uni.request({
url:'http://www.xxxxxxr.com/apkconfig.json',
method : "GET",
data : {},
success:(res) => {
var datalist = res.data.gaozhou
if(datalist){ //数据集正常读取
//console.log(datalist[0].serverip)
var serverip = datalist[0].serverip
uni.setStorageSync('serverip', serverip) //保存到本地缓存
uni.showToast({
title: "参数设置成功,请完全关闭软件再次运行!",
icon:'none',
duration: 5000
});
} else {
uni.showToast({
title: "读取配置文件失败!",
icon:'none',
duration: 2000
});
}
},
fail:() => {
uni.showToast({
title: "访问云服务器失败!",
icon:'none',
duration: 2000
});
}
})
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
}
.radiogroup{
display: flex;
flex-direction: column;
margin: 30rpx;
}
.radio{
margin: 30rpx;
}
.inputip{
display: flex;
flex-direction: row;
margin-left: 50rpx;
}
.inputtext{
border-style:solid;
border-color: #9d9d9d;
}
.btn{
margin: 100rpx;
}
</style>
自动配置文件格式 apkconfig.json ,上传到web服务器。
{
"gaozhou":[ //高州app配置
{
"version":"1.0",
"serverip":"192.168.1.174"
}
],
"nanwang":[ //南网app配置
{
"version":"2.0"
}
]
}