nodejs + uniapp 开发UDP广播控制视频播放

29 篇文章 1 订阅

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"
		}
	]
}
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
首先,需要了解什么是 JSAPI 支付,它是微信支付方式之一,允许商户在移动端网页或者微信公众号内完成支付,用户可以在不离开微信的情况下完成支付操作。 其次,为了接收支付成功通知,我们需要在微信商户平台设置回调通知地址,当用户支付成功后,微信会向该地址发送 XML 格式的通知数据。因此,我们需要编写一个接口来处理这些通知数据。 以下是实现该接口的步骤: 1. 确定回调通知地址,可以是一个 API 接口,例如:/api/payment/notify。 2. 使用 midwayjs 作为开发框架,它集成了 koa 框架,并提供了一些更加便捷的开发方式。 3. 使用 typescript 语言编写代码,可以提高代码质量和可读性。 4. 在接口中使用 koa-bodyparser 中间件来解析 XML 数据,将其转换为 JSON 格式,方便后续处理。 5. 验证通知数据的签名,确保数据的安全性和完整性。 6. 根据通知数据中的订单号查询订单信息,确保订单的合法性。 7. 根据订单信息更新相应的业务数据,例如订单状态等。 8. 返回响应给微信服务器,告知通知处理结果,必须返回 success 或者 fail 。 以下是一个简单的示例代码: ```typescript import { Context } from 'midway'; import * as parser from 'koa-xml-body'; import * as WechatPay from 'wechatpay-nodejs'; import * as config from '../config'; export default class PaymentController { async notify(ctx: Context) { const xml = ctx.request.body; // 解析 XML 数据 const data = await parser(xml); // 验证签名 const wp = new WechatPay(config.wechatPay); const valid = wp.verifySign(data); if (!valid) { ctx.body = '<xml><return_code><![CDATA[FAIL]]></return_code></xml>'; return; } // 处理订单信息 const order = await getOrder(data.out_trade_no); if (!order) { ctx.body = '<xml><return_code><![CDATA[FAIL]]></return_code></xml>'; return; } // 更新订单状态 order.status = 'paid'; await updateOrder(order); // 返回成功响应 ctx.body = '<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>'; } } ``` 需要注意的是,以上代码仅为示例代码,实际开发中还需要考虑更多的细节和异常情况,例如网络异常、数据库错误等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值