init commit
This commit is contained in:
commit
1eed1428f2
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
32
README.md
Normal file
32
README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# BLive Coyote
|
||||||
|
郊狼B站直播玩法:送礼物控制郊狼主机输出
|
||||||
|
|
||||||
|
## 项目简介
|
||||||
|
> [!TIP]
|
||||||
|
> 本项目仍在开发中,目前可体验基础功能。
|
||||||
|
>
|
||||||
|
> 本项目为个人学习项目,如有错误敬请批评指正,欢迎 PR。
|
||||||
|
|
||||||
|
本项目以 [Bilibili 直播&互玩 JavaScript Demo](https://open-live.bilibili.com/document/a7bd5377-ad7d-a273-25ae-28caf37a7a85) 和 [DG-LAB SOCKET控制-控制端开源](https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE/tree/main/socket) 为基础,实现了直播间送礼物控制郊狼主机输出的功能。
|
||||||
|
|
||||||
|
- 郊狼 WebSocket 后端请参考:[DG-LAB-OPENSOURCE](https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE/tree/main/socket/BackEnd(Node))
|
||||||
|
- 哔哩哔哩直播互动玩法服务端请参考:[JavaScript Demo](https://open-live.bilibili.com/document/a7bd5377-ad7d-a273-25ae-28caf37a7a85)
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
```
|
||||||
|
Client
|
||||||
|
├─public
|
||||||
|
│ └─css
|
||||||
|
└─src
|
||||||
|
├─assets
|
||||||
|
├─socket
|
||||||
|
└─types
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目启动
|
||||||
|
```bash
|
||||||
|
cd Client
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
主播身份码及 app_id 获取请参考 [开放平台-直播&互玩接入文档:常见问题](https://open-live.bilibili.com/document/5dffc297-6fd2-41ff-bd45-6e8b89e2a68e)
|
17
index.html
Normal file
17
index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<link rel="stylesheet" href="/css/style.css">
|
||||||
|
<link rel="stylesheet" href="/css/notyf.min.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
3283
package-lock.json
generated
Normal file
3283
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "poros-demo-client",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port 8888 --host 0.0.0.0 ",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.2.25"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@originjs/vite-plugin-commonjs": "1.0.3",
|
||||||
|
"@types/node": "17.0.25",
|
||||||
|
"@vitejs/plugin-vue": "2.3.0",
|
||||||
|
"axios": "0.26.1",
|
||||||
|
"typescript": "4.5.4",
|
||||||
|
"vite": "2.9.0",
|
||||||
|
"vue-tsc": "0.29.8",
|
||||||
|
"ws": "8.17.0",
|
||||||
|
"qrcode": "1.5.3",
|
||||||
|
"notyf": "3.0.0"
|
||||||
|
}
|
||||||
|
}
|
1
public/css/notyf.min.css
vendored
Normal file
1
public/css/notyf.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
611
public/css/style.css
Normal file
611
public/css/style.css
Normal file
@ -0,0 +1,611 @@
|
|||||||
|
/* Copyright 2013 The Chromium Authors. All rights reserved.
|
||||||
|
* Use of this source code is governed by a BSD-style license that can be
|
||||||
|
* found in the LICENSE file. */
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-offline {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #fce9a7;
|
||||||
|
color: #000;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-ms-border-radius: 3px;
|
||||||
|
-o-border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #ffe668;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-color: #fce9a7;
|
||||||
|
border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-ms-border-radius: 3px;
|
||||||
|
-o-border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Offline page */
|
||||||
|
|
||||||
|
.offline .interstitial-wrapper {
|
||||||
|
color: #2b2b2b;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.55;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 600px;
|
||||||
|
padding-top: 200px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline .runner-container {
|
||||||
|
height: 150px;
|
||||||
|
max-width: 600px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 180px;
|
||||||
|
width: 44px;
|
||||||
|
transition: top 0.1s ease-in-out;
|
||||||
|
-webkit-transition: top 0.1s ease-in-out;
|
||||||
|
-moz-transition: top 0.1s ease-in-out;
|
||||||
|
-ms-transition: top 0.1s ease-in-out;
|
||||||
|
-o-transition: top 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline .runner-canvas {
|
||||||
|
height: 150px;
|
||||||
|
max-width: 600px;
|
||||||
|
opacity: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline .controller {
|
||||||
|
background: rgba(247, 247, 247, .1);
|
||||||
|
height: 100vh;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100vw;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#offline-resources {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
|
||||||
|
.suggested-left>#control-buttons,
|
||||||
|
.suggested-right>#control-buttons {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snackbar {
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 350px) {
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-offline {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interstitial-wrapper {
|
||||||
|
margin-top: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-wrapper {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 600px) and (max-width: 736px) and (orientation: landscape) {
|
||||||
|
.offline .interstitial-wrapper {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 420px) and (max-width: 736px) and (min-height: 240px) and (max-height: 420px) and (orientation:landscape) {
|
||||||
|
.interstitial-wrapper {
|
||||||
|
margin-bottom: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-height: 240px) and (orientation: landscape) {
|
||||||
|
.offline .interstitial-wrapper {
|
||||||
|
margin-bottom: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-offline {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 320px) and (orientation: landscape) {
|
||||||
|
.icon-offline {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline .runner-container {
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 240px) {
|
||||||
|
.interstitial-wrapper {
|
||||||
|
overflow: inherit;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arcade-mode,
|
||||||
|
.arcade-mode .runner-container,
|
||||||
|
.arcade-mode .runner-canvas {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arcade-mode #buttons,
|
||||||
|
.arcade-mode #main-content {
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arcade-mode .interstitial-wrapper {
|
||||||
|
height: 100vh;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arcade-mode .runner-container {
|
||||||
|
left: 0;
|
||||||
|
margin: auto;
|
||||||
|
right: 0;
|
||||||
|
transform-origin: top center;
|
||||||
|
transition: transform 250ms cubic-bezier(0.4, 0, 1, 1) 400ms;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qrcode-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.85);
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1002;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qrcode-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1002;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qrcode {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1002;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qrcode-text {
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qrcode-text p {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
/* 自动换行 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-qrcode {
|
||||||
|
color: #000000;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #ffe99d;
|
||||||
|
padding: 5px 30px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 18%;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 140px;
|
||||||
|
min-width: 500px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 145px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #ffe99d;
|
||||||
|
border-radius: 5px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
-ms-border-radius: 5px;
|
||||||
|
-o-border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 20px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
width: 15%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #ffe99d;
|
||||||
|
border-radius: 5px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
-ms-border-radius: 5px;
|
||||||
|
-o-border-radius: 5px;
|
||||||
|
right: 0;
|
||||||
|
color: #ffe99d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: rgb(255, 67, 67);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-background {
|
||||||
|
background-color: rgb(255, 70, 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-btn {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-btn button:first-child {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connect-btn button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-ms-border-radius: 3px;
|
||||||
|
-o-border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dg-controller {
|
||||||
|
width: 85%;
|
||||||
|
z-index: 1000;
|
||||||
|
color: #ffe99d;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #ffe99d;
|
||||||
|
border-radius: 5px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
-ms-border-radius: 5px;
|
||||||
|
-o-border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1020px */
|
||||||
|
@media screen and (max-width: 1020px) {
|
||||||
|
.dg-controller {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-container {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 792px) {
|
||||||
|
.dg-controller {
|
||||||
|
width: 72%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-container {
|
||||||
|
width: 28%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.dg-controller {
|
||||||
|
width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-container {
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dg-controller {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
/* Firefox */
|
||||||
|
scrollbar-color: #ffe99d transparent;
|
||||||
|
/* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dg-controller::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
/* Chrome, Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dg-controller::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
/* Chrome, Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dg-controller::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #ffe99d;
|
||||||
|
/* Chrome, Safari */
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
.dg-controller {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #ffe99d transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputTime {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputTime input,
|
||||||
|
.btn-container input {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container>* {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-msg {
|
||||||
|
margin-top: 10px;
|
||||||
|
min-width: 200px;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-game {
|
||||||
|
color: #ffe99d;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
left: 20px;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-title {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-tips {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-hide {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 12px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-ms-border-radius: 3px;
|
||||||
|
-o-border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 46px;
|
||||||
|
height: 22px;
|
||||||
|
background-color: #3b3b3b;
|
||||||
|
border-radius: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-flex-shrink: 0;
|
||||||
|
/* Safari 和 Chrome */
|
||||||
|
-ms-flex-negative: 0;
|
||||||
|
/* IE 10+ */
|
||||||
|
flex-shrink: 0;
|
||||||
|
-moz-box-flex: 0;
|
||||||
|
/* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
left: 5px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: #ddd;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: .4s;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
-moz-transition: .4s;
|
||||||
|
-ms-transition: .4s;
|
||||||
|
-o-transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container.on {
|
||||||
|
background-color: #fce9a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-container.on .toggle-switch {
|
||||||
|
transform: translateX(20px);
|
||||||
|
-webkit-transform: translateX(20px);
|
||||||
|
-moz-transform: translateX(20px);
|
||||||
|
-ms-transform: translateX(20px);
|
||||||
|
-o-transform: translateX(20px);
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-img {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #333;
|
||||||
|
color: #ffe99d;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: none;
|
||||||
|
/* 默认隐藏 */
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
padding: 10px 30px 30px 30px;
|
||||||
|
color: #ffe99d;
|
||||||
|
background-color: rgba(0, 0, 0);
|
||||||
|
border: 1px solid #ffe99d;
|
||||||
|
border-radius: 10px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
-moz-transform: translate(-50%, -50%);
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
-o-transform: translate(-50%, -50%);
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
-ms-border-radius: 10px;
|
||||||
|
-o-border-radius: 10px;
|
||||||
|
z-index: 1003;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information a:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.85);
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
visibility: hidden;
|
||||||
|
z-index: 1002;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information {
|
||||||
|
color: #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information h2 {
|
||||||
|
color: #ffe99d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information .notify{
|
||||||
|
color: #ffe99d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-close {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-close-btn {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #000;
|
||||||
|
border: 1px solid #ffe99d;
|
||||||
|
color: #ffe99d;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px 30px;
|
||||||
|
border-radius: 5px;
|
||||||
|
-webkit-border-radius: 5px;
|
||||||
|
-moz-border-radius: 5px;
|
||||||
|
-ms-border-radius: 5px;
|
||||||
|
-o-border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-close-btn:hover {
|
||||||
|
background-color: #ffe99d;
|
||||||
|
color: #000;
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
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[]
|
||||||
|
}
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2016",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"types": ["node"],
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
8
tsconfig.node.json
Normal file
8
tsconfig.node.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
17
vite.config.ts
Normal file
17
vite.config.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { defineConfig } from "vite"
|
||||||
|
import vue from "@vitejs/plugin-vue"
|
||||||
|
import { viteCommonjs } from "@originjs/vite-plugin-commonjs"
|
||||||
|
import * as path from "path"
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
// https: true
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [vue(), viteCommonjs()]
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user