init commit
This commit is contained in:
229
src/App.vue
Normal file
229
src/App.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
|
||||
import { ref } from "vue"
|
||||
import axios from "axios"
|
||||
import { createSocket, destroySocket } from "./socket/index"
|
||||
import { createCoyoteSocket, closeCoyoteSocket, qrcodeSrc, qrcodeShow } from "./socket/coyote"
|
||||
import { Notyf } from 'notyf'
|
||||
|
||||
const notyf = new Notyf({ duration: 3000 })
|
||||
|
||||
|
||||
// API
|
||||
const api = axios.create({
|
||||
baseURL: "http://localhost:3000",
|
||||
})
|
||||
|
||||
// 替换你的主播身份码
|
||||
const codeId = ref("")
|
||||
// 替换你的app应用 [这里测试为互动游戏]
|
||||
const appId = ref("")
|
||||
// [向 node server请求接口后自动返回]
|
||||
const gameId = ref("")
|
||||
// v2改为server response 服务器返回websocket信息,而非手动获取
|
||||
const authBody = ref("")
|
||||
const wssLinks = ref([])
|
||||
// heartBeat Timer
|
||||
const heartBeatTimer = ref<NodeJS.Timer>()
|
||||
// be ready
|
||||
clearInterval(heartBeatTimer.value!)
|
||||
|
||||
/**
|
||||
* 测试请求鉴权接口
|
||||
*/
|
||||
const getAuth = () => {
|
||||
api.post("/getAuth", {})
|
||||
.then(({ data }) => {
|
||||
console.log("-----鉴权成功-----")
|
||||
notyf.success({ message: "鉴权成功" })
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----鉴权失败-----")
|
||||
notyf.error({ message: "鉴权失败" })
|
||||
})
|
||||
}
|
||||
|
||||
const heartBeatThis = (game_id) => {
|
||||
// 心跳 是否成功
|
||||
api.post("/gameHeartBeat", {
|
||||
game_id,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
console.log("-----心跳成功-----")
|
||||
console.log("返回:", data)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----心跳失败-----")
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @comment 注意所有的接口基于鉴权成功后才能正确返回
|
||||
* 测试请求游戏开启接口
|
||||
*/
|
||||
const gameStart = () => {
|
||||
api.post("/gameStart", {
|
||||
code: codeId.value,
|
||||
app_id: Number(appId.value),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
const res = data.data
|
||||
const { game_info, websocket_info } = res
|
||||
const { auth_body, wss_link } = websocket_info
|
||||
authBody.value = auth_body
|
||||
wssLinks.value = wss_link
|
||||
console.log("-----游戏开始成功-----")
|
||||
console.log("返回GameId:", game_info)
|
||||
notyf.success({ message: "游戏开始成功" })
|
||||
gameId.value = game_info.game_id
|
||||
// v2改为20s请求心跳一次,不然60s会自动关闭
|
||||
heartBeatTimer.value = setInterval(() => {
|
||||
heartBeatThis(game_info.game_id)
|
||||
}, 20000)
|
||||
handleCreateSocket()
|
||||
} else {
|
||||
console.log("-----游戏开始失败-----")
|
||||
console.log("原因:", data)
|
||||
notyf.error({ message: "游戏开始失败" })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----游戏开始失败-----")
|
||||
console.log(err)
|
||||
notyf.error({ message: "游戏开始失败" })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @comment 基于gameStart成功后才会关闭正常,否则获取不到game_id
|
||||
* 测试请求游戏关闭接口
|
||||
*/
|
||||
const gameEnd = () => {
|
||||
api.post("/gameEnd", {
|
||||
game_id: gameId.value,
|
||||
app_id: Number(appId.value),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
console.log("-----游戏关闭成功-----")
|
||||
console.log("返回:", data)
|
||||
// 清空长链
|
||||
authBody.value = ""
|
||||
wssLinks.value = []
|
||||
clearInterval(heartBeatTimer.value!) // 这里会报个错误,加了个非空断言
|
||||
handleDestroySocket()
|
||||
console.log("-----心跳关闭成功-----")
|
||||
notyf.success({ message: "游戏关闭成功" })
|
||||
} else {
|
||||
console.log("-----游戏关闭失败-----")
|
||||
console.log("原因:", data)
|
||||
notyf.error({ message: "游戏关闭失败" })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("-----游戏关闭失败-----")
|
||||
console.log(err)
|
||||
notyf.error({ message: "游戏关闭失败" })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试创建长长连接接口
|
||||
*/
|
||||
const handleCreateSocket = () => {
|
||||
if (authBody.value && wssLinks.value) {
|
||||
createSocket(authBody.value, wssLinks.value)
|
||||
console.log("-----长连接创建成功-----")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试销毁长长连接接口
|
||||
*/
|
||||
const handleDestroySocket = () => {
|
||||
destroySocket()
|
||||
console.log("-----长连接销毁成功-----")
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试按钮
|
||||
*/
|
||||
const test = () => {
|
||||
let map = {
|
||||
"123": "AAWWWWW",
|
||||
"123456": "aaaaaaaa",
|
||||
}
|
||||
notyf.success({ message: '测试吐司', duration: 3000, ripple: true })
|
||||
notyf.success({ message: map[123], duration: 3000, ripple: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示二维码
|
||||
*/
|
||||
const showqrcode = () => {
|
||||
qrcodeShow.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏二维码
|
||||
*/
|
||||
const hideqrcode = () => {
|
||||
qrcodeShow.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="qrcode-overlay" v-show="qrcodeShow">
|
||||
<div id="qrcode-container">
|
||||
<div id="qrcode-text">
|
||||
<p>使用DG-LAB APP扫码建立WebSocket链接</p>
|
||||
<p>请先开始游戏与直播间建立连接</p>
|
||||
</div>
|
||||
<div id="qrcode-img">
|
||||
<img id="qrcode" :src="qrcodeSrc">
|
||||
</div>
|
||||
<div class="close-qrcode" @click="hideqrcode">关闭</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>游戏设置</h2>
|
||||
<div class="form">
|
||||
<label>主播身份码</label>
|
||||
<input type="password" placeholder="填写主播身份码" v-model="codeId"/>
|
||||
<label>app_id</label>
|
||||
<input type="text" placeholder="填写 app_id" v-model="appId" />
|
||||
<button @click="gameStart">游戏开始</button>
|
||||
<button @click="gameEnd">游戏结束</button>
|
||||
<button @click="createCoyoteSocket">连接郊狼</button>
|
||||
<button @click="closeCoyoteSocket">断开郊狼</button>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>调试选项</h2>
|
||||
<div class="form">
|
||||
<button @click="getAuth">鉴权</button>
|
||||
<button @click="test">测试</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
color: #fff;
|
||||
margin: 60px 50px;
|
||||
}
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form input,
|
||||
.form button {
|
||||
width: 300px;
|
||||
height: 50px;
|
||||
margin: 10px 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
8
src/assets/danmaku-websocket.min.js
vendored
Normal file
8
src/assets/danmaku-websocket.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
19
src/assets/dataMap.ts
Normal file
19
src/assets/dataMap.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// Desc: 用于存放数据映射表
|
||||
|
||||
// 礼物对应的波形
|
||||
const waveData = {
|
||||
"31037": `["0A0A0A0A00000000","0A0A0A0A0A0A0A0A","0A0A0A0A14141414","0A0A0A0A1E1E1E1E","0A0A0A0A28282828","0A0A0A0A32323232","0A0A0A0A3C3C3C3C","0A0A0A0A46464646","0A0A0A0A50505050","0A0A0A0A5A5A5A5A","0A0A0A0A64646464"]`,
|
||||
"31164": `["0A0A0A0A00000000","0D0D0D0D0F0F0F0F","101010101E1E1E1E","1313131332323232","1616161641414141","1A1A1A1A50505050","1D1D1D1D64646464","202020205A5A5A5A","2323232350505050","262626264B4B4B4B","2A2A2A2A41414141"]`,
|
||||
"32609": `["4A4A4A4A64646464","4545454564646464","4040404064646464","3B3B3B3B64646464","3636363664646464","3232323264646464","2D2D2D2D64646464","2828282864646464","2323232364646464","1E1E1E1E64646464","1A1A1A1A64646464"]`
|
||||
}
|
||||
|
||||
// 礼物id和礼物名的对应关系
|
||||
const giftData = {
|
||||
"31036": "小花花",
|
||||
"31039": "牛哇牛哇",
|
||||
"32609": "棒棒糖",
|
||||
"31037": "打call",
|
||||
"31164": "粉丝团灯牌",
|
||||
}
|
||||
|
||||
export { waveData }
|
||||
1
src/assets/notyf.min.css
vendored
Normal file
1
src/assets/notyf.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
9
src/env.d.ts
vendored
Normal file
9
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
5
src/main.ts
Normal file
5
src/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from "vue"
|
||||
import App from "./App.vue"
|
||||
|
||||
createApp(App).mount("#app")
|
||||
|
||||
171
src/socket/coyote.ts
Normal file
171
src/socket/coyote.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import {ref} from "vue";
|
||||
import { Notyf } from 'notyf'
|
||||
import { waveData } from "../assets/dataMap";
|
||||
|
||||
const notyf = new Notyf({ duration: 4000 })
|
||||
const QRCode = require('qrcode')
|
||||
|
||||
let channelAStrength = 0; // A通道强度
|
||||
let channelBStrength = 0; // B通道强度
|
||||
|
||||
let connectionId = ""; // 从接口获取的连接标识符
|
||||
let targetWSId = ""; // 发送目标
|
||||
let fangdou = 500; //500毫秒防抖
|
||||
let fangdouSetTimeOut; // 防抖定时器
|
||||
let followAStrength = false; //跟随AB软上限
|
||||
let followBStrength = false;
|
||||
let wsConn; // 全局ws链接
|
||||
const feedBackMsg = {
|
||||
"feedback-0": "A通道:○",
|
||||
"feedback-1": "A通道:△",
|
||||
"feedback-2": "A通道:□",
|
||||
"feedback-3": "A通道:☆",
|
||||
"feedback-4": "A通道:⬡",
|
||||
"feedback-5": "B通道:○",
|
||||
"feedback-6": "B通道:△",
|
||||
"feedback-7": "B通道:□",
|
||||
"feedback-8": "B通道:☆",
|
||||
"feedback-9": "B通道:⬡",
|
||||
}
|
||||
|
||||
const qrcodeSrc = ref("")
|
||||
const qrcodeShow = ref(false)
|
||||
QRCode.toDataURL("https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#ws://39.108.168.199:9999/", function (err, url) {
|
||||
//console.log(url)
|
||||
qrcodeSrc.value = url
|
||||
})
|
||||
|
||||
function createCoyoteSocket() {
|
||||
wsConn = new WebSocket('ws://coyote.babyfang.cn:9999/');
|
||||
wsConn.onopen = function (event) {
|
||||
console.log("WebSocket连接已建立");
|
||||
}
|
||||
|
||||
wsConn.onmessage = function (event) {
|
||||
let msg = {} as any
|
||||
try {
|
||||
msg = JSON.parse(event.data);
|
||||
} catch (e) {
|
||||
console.log(event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(event.data)
|
||||
|
||||
switch (msg.type) {
|
||||
case 'bind':
|
||||
if (!msg.targetId) {
|
||||
connectionId = msg.clientId;
|
||||
console.log(`收到clientId:${connectionId}`);
|
||||
QRCode.toDataURL("https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#ws://coyote.babyfang.cn:9999/" + connectionId, function (err, url) {
|
||||
//console.log(url)
|
||||
qrcodeSrc.value = url
|
||||
})
|
||||
qrcodeShow.value = true
|
||||
} else {
|
||||
if (msg.clientId != connectionId) {
|
||||
console.log("错误的clientId")
|
||||
return;
|
||||
}
|
||||
targetWSId = msg.targetId;
|
||||
console.log("收到targetId: " + msg.targetId + ", msg: " + msg.message);
|
||||
qrcodeShow.value = false
|
||||
notyf.success({message: "郊狼连接成功"})
|
||||
}
|
||||
break;
|
||||
case 'break':
|
||||
if (msg.targetId != targetWSId)
|
||||
return;
|
||||
console.log("收到断开连接指令")
|
||||
notyf.error({ message: "收到断开连接指令" })
|
||||
//location.reload();
|
||||
break;
|
||||
case 'error':
|
||||
if (msg.targetId != targetWSId)
|
||||
return;
|
||||
console.log("对方已断开,code:" + msg.message)
|
||||
notyf.error({ message: "对方已断开(" + msg.message + ")" })
|
||||
break;
|
||||
case 'msg':
|
||||
const result: { type: string; numbers: number[] }[] = []
|
||||
if(msg.message.includes("strength")) {
|
||||
const numbers = msg.message.match(/\d+/g).map(Number)
|
||||
result.push({ type: "strength", numbers: numbers })
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendWsMsg(messageObj) {
|
||||
messageObj.clientId = connectionId;
|
||||
messageObj.targetId = targetWSId;
|
||||
if (!messageObj.hasOwnProperty('type'))
|
||||
messageObj.type = "msg";
|
||||
wsConn.send(JSON.stringify((messageObj)));
|
||||
}
|
||||
|
||||
function addOrIncrease(type, channelIndex, strength) {
|
||||
// 1 减少一 2 增加一 3 设置到
|
||||
// channel:1-A 2-B
|
||||
// 获取当前频道元素和当前值
|
||||
let channelStrength = channelIndex === 1 ? channelAStrength : channelBStrength;
|
||||
|
||||
// 如果是设置操作
|
||||
if (type === 3) {
|
||||
channelStrength = strength; //固定为0
|
||||
}
|
||||
// 减少一
|
||||
else if (type === 1) {
|
||||
channelStrength = Math.max(channelStrength - strength, 0);
|
||||
}
|
||||
// 增加一
|
||||
else if (type === 2) {
|
||||
channelStrength = Math.min(channelStrength + strength, 200);
|
||||
}
|
||||
|
||||
// 构造消息对象并发送
|
||||
const data = { type, strength: channelStrength, message: "set channel", channel: channelIndex };
|
||||
console.log(data)
|
||||
sendWsMsg(data);
|
||||
}
|
||||
|
||||
function clearAB(channelIndex) {
|
||||
const data = { type: 4, message: "clear-" + channelIndex }
|
||||
sendWsMsg(data);
|
||||
}
|
||||
|
||||
function sendWaveData(timeA, timeB, waveA, waveB) {
|
||||
if (fangdouSetTimeOut) {
|
||||
return
|
||||
}
|
||||
|
||||
const msg1 = `A:${waveData[waveA]}`
|
||||
const msg2 = `B:${waveData[waveB]}`
|
||||
const data = {
|
||||
type: "clientMsg", message: msg1, message2: msg2, time1: timeA, time2: timeB
|
||||
}
|
||||
|
||||
sendWsMsg(data)
|
||||
|
||||
fangdouSetTimeOut = setTimeout(() => {
|
||||
clearTimeout(fangdouSetTimeOut)
|
||||
fangdouSetTimeOut = null
|
||||
}, fangdou)
|
||||
}
|
||||
|
||||
function closeCoyoteSocket() {
|
||||
try {
|
||||
wsConn.close()
|
||||
}
|
||||
catch (e) {
|
||||
notyf.error( {message: "郊狼连接断开失败"} )
|
||||
console.log(e)
|
||||
return
|
||||
}
|
||||
wsConn = null
|
||||
notyf.success( {message: "郊狼连接已断开"} )
|
||||
}
|
||||
|
||||
|
||||
export { createCoyoteSocket, closeCoyoteSocket, sendWsMsg, sendWaveData, addOrIncrease, clearAB, qrcodeSrc, qrcodeShow, channelAStrength, channelBStrength }
|
||||
133
src/socket/index.ts
Normal file
133
src/socket/index.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import DanmakuWebSocket from "../assets/danmaku-websocket.min.js"
|
||||
import { Notyf } from 'notyf'
|
||||
import { closeCoyoteSocket, addOrIncrease, sendWaveData } from "./coyote"
|
||||
|
||||
let ws: DanmakuWebSocket
|
||||
const notyf = new Notyf({ duration: 4000 })
|
||||
|
||||
/**
|
||||
* 创建socket长连接
|
||||
* @param authBody
|
||||
* @param wssLinks
|
||||
*/
|
||||
function createSocket(authBody: string, wssLinks: string[]) {
|
||||
const opt = {
|
||||
...getWebSocketConfig(authBody, wssLinks),
|
||||
// 收到消息,
|
||||
onReceivedMessage: (res) => {
|
||||
console.log("收到"+ res.cmd +"消息:")
|
||||
//console.log(res.data.uname + "(大航海" +res.data.guard_level + "级):" + res.data.msg)
|
||||
|
||||
// if (res.data.msg == "#UPA1") {
|
||||
// try {
|
||||
// addOrIncrease(2, 1, 1)
|
||||
// notyf.success("A通道强度增加成功")
|
||||
// }
|
||||
// catch (e) {
|
||||
// console.log(e)
|
||||
// notyf.error("A通道强度增加失败")
|
||||
// }
|
||||
// }
|
||||
|
||||
if (res.cmd == "LIVE_OPEN_PLATFORM_SEND_GIFT") {
|
||||
if (res.data.gift_id == 31036) {
|
||||
// 小花花:减强度1
|
||||
try {
|
||||
addOrIncrease(1, 1, 1)
|
||||
addOrIncrease(1, 2, 1)
|
||||
notyf.success("收到花花,强度-1")
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
notyf.error("强度操作失败!")
|
||||
}
|
||||
} else if (res.data.gift_id == 31039) {
|
||||
// 牛哇牛哇:加强度1
|
||||
try {
|
||||
addOrIncrease(2, 1, 1)
|
||||
addOrIncrease(2, 2, 1)
|
||||
notyf.success("收到牛牛,强度+1")
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
notyf.error("强度操作失败!")
|
||||
}
|
||||
} else {
|
||||
// 其他礼物,发送波形数据
|
||||
try {
|
||||
sendWaveData(5, 5, res.data.gift_id, res.data.gift_id)
|
||||
notyf.success("收到礼物" + res.data.gift_name)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
notyf.error("发送波形数据失败!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(res)
|
||||
},
|
||||
// 收到心跳处理回调
|
||||
onHeartBeatReply: (data) => console.log("收到心跳处理回调:", data),
|
||||
onError: (data) => console.log("error", data),
|
||||
onListConnectError: () => {
|
||||
console.log("list connect error")
|
||||
destroySocket()
|
||||
},
|
||||
}
|
||||
|
||||
if (!ws) {
|
||||
ws = new DanmakuWebSocket(opt)
|
||||
}
|
||||
|
||||
return ws
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取websocket配置信息
|
||||
* @param authBody
|
||||
* @param wssLinks
|
||||
*/
|
||||
function getWebSocketConfig(authBody: string, wssLinks: string[]) {
|
||||
const url = wssLinks[0]
|
||||
const urlList = wssLinks
|
||||
const auth_body = JSON.parse(authBody)
|
||||
return {
|
||||
url,
|
||||
urlList,
|
||||
customAuthParam: [
|
||||
{
|
||||
key: "key",
|
||||
value: auth_body.key,
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
key: "group",
|
||||
value: auth_body.group,
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
rid: auth_body.roomid,
|
||||
protover: auth_body.protoover,
|
||||
uid: auth_body.uid,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁websocket
|
||||
*/
|
||||
function destroySocket() {
|
||||
console.log("destroy1")
|
||||
ws && ws.destroy()
|
||||
ws = undefined
|
||||
console.log("destroy2")
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取websocket实例
|
||||
*/
|
||||
function getWsClient() {
|
||||
return ws
|
||||
}
|
||||
|
||||
export { createSocket, destroySocket, getWebSocketConfig, getWsClient }
|
||||
19
src/types/index.d.ts
vendored
Normal file
19
src/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
declare interface ISocketData {
|
||||
// ip地址
|
||||
ip: number[]
|
||||
|
||||
// host地址 可能是ip 也可能是域名。
|
||||
host: string[]
|
||||
|
||||
// 长连使用的请求json体 第三方无需关注内容,建立长连时使用即可。
|
||||
auth_body: string
|
||||
|
||||
// tcp 端口号
|
||||
tcp_port: number[]
|
||||
|
||||
// ws 端口号
|
||||
ws_port: number[]
|
||||
|
||||
// wss 端口号
|
||||
wss_port: number[]
|
||||
}
|
||||
Reference in New Issue
Block a user