Files
tkAiPage/src/views/VideoStream.vue

1606 lines
60 KiB
Vue
Raw Normal View History

2025-07-01 21:36:08 +08:00
<template>
<div class="main">
2025-08-07 21:35:27 +08:00
<el-scrollbar class="left"> <!-- 左边栏 -->
<div class="center-line"> <!-- 左边栏按钮 -->
<div v-for="(btn, index) in buttons" :key="index" style="width: 100%;">
2025-08-11 22:00:45 +08:00
<div v-if="btn.show?.()" class="left-button" :style="btn.style ? btn.style() : {}" @click="btn.onClick"
@mouseenter="hoverIndex = index" @mouseleave="hoverIndex = null">
2025-08-07 21:35:27 +08:00
<img :src="hoverIndex === index ? btn.img.hover : btn.img.normal" alt="">
{{ btn.label }}
</div>
</div>
2025-07-01 21:36:08 +08:00
</div>
</el-scrollbar>
2025-08-07 21:35:27 +08:00
<!-- 中间手机区域 -->
2025-07-01 21:36:08 +08:00
<div class="content" @click.self="selectedDevice = 999">
<div class="video-container" @click.self="selectedDevice = 999" v-for="(device, index) in deviceInformation"
:key="device.udid" @mouseup="(e) => handleCanvasup(device.udid, e, index)">
<div class="video-canvas">
<video :ref="(el) => (videoElement[device.udid] = el)" autoplay muted playsinline
2025-07-15 13:45:36 +08:00
:style="getVideoStyle(index)" @click.stop="selectedDevice = index"></video>
2025-07-01 21:36:08 +08:00
<canvas class="canvas" v-show="selectedDevice == index" :ref="(el) => (canvasRef[device.udid] = el)"
@mousemove.stop="(e) => handleMouseMove(device.udid, e, index)"
2025-08-07 21:35:27 +08:00
@mousedown.stop="(e) => handleCanvasdown(device.udid, e, index)" :style="{
transform: getTransformStyle(index)
}">
2025-07-01 21:36:08 +08:00
</canvas>
</div>
2025-08-07 21:35:27 +08:00
<div class="input-info" v-show="selectedDevice == index"
:style="{ left: phone.width * 1.4 + 4 + '0px', transform: getTransformStyle(index), }">
<div class="app-button" @click="resetApp(device.udid, index)">重置应用</div>
<div class="app-button"
@click="wsActions.clickCopyList(device.udid, index); openShowChat = true; istranslate = true">
翻译本页对话</div>
<div class="app-button" @click="wsActions.clickCopyList(device.udid, index); openShowChat = true;">
回复消息</div>
<!-- <div class="app-button" @click="wsActions.getSize(device.udid, index)">获取屏幕尺寸</div> -->
<div class="app-button" @click="wsActions.test(device.udid, index)">打印ui节点树</div>
<div class="app-button" @click="wsActions.isOneLive(device.udid, index)">判断单人还是双人</div>
<div class="app-button" @click="wsActions.slideDown(device.udid, index)">下滑</div>
<div class="app-button" @click="wsActions.killNow(device.udid, index)">关闭当前应用</div>
<div class="app-button" @click="chooseFile(device.udid, index, 1, wsActions)">安装 APK
文件</div>
<div class="app-button" @click="chooseFile(device.udid, index, 2, wsActions)">
传送文件</div>
2025-07-01 21:36:08 +08:00
<div style="display: flex;">
2025-08-07 21:35:27 +08:00
<input style="border: 1px solid #000;margin:0px 14px;" v-model="textContent[index]" type="text"></input>
<div class="app-button" style="margin: 0px;height: 40px;width: 60px;font-size: 14px;"
@click="setComText(index)">发送
</div>
2025-07-01 21:36:08 +08:00
</div>
2025-08-07 21:35:27 +08:00
<!-- <div class="app-button" @click="wsActions.isHost(device.udid, index)">一键养号</div> -->
2025-07-01 21:36:08 +08:00
</div>
</div>
</div>
2025-08-07 21:35:27 +08:00
<div class="right" @click.self="selectedDevice = 999">
<div style="margin: 14px;"></div>
2025-07-15 13:45:36 +08:00
<ChatDialog :visible="openShowChat" :messages="chatList" />
2025-07-01 21:36:08 +08:00
</div>
<MultiLineInputDialog v-model:visible="showDialog" :initialText='""' :title="dialogTitle" :index="selectedDevice"
2025-08-11 22:00:45 +08:00
@confirm="onDialogConfirm" @cancel="stop" />
2025-07-01 21:36:08 +08:00
</div>
</template>
<script setup>
2025-08-14 19:52:34 +08:00
import { ref, onMounted, onUnmounted, onBeforeUnmount, watch, inject, nextTick } from "vue";
2025-07-01 21:36:08 +08:00
import VideoConverter from "h264-converter";
import { useRouter } from 'vue-router';
2025-08-07 21:35:27 +08:00
import {
setphoneXYinfo, getphoneXYinfo, getUser,
getHostList, setHostList, getContentpriList,
setContentpriList, getContentList, setContentList,
setsessionId, getsessionId
} from '@/stores/storage'
2025-07-01 21:36:08 +08:00
import { toBufferBtn, stringToUtf8ByteArray, getClipboard, setClipboard, bufferToString, startsWithHeader, trimLongArray, base64ToBinary, toBuffer } from '@/utils/bufferUtils';
import { createWsActions } from '@/utils/wsActions';
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
2025-07-30 13:34:49 +08:00
import { chat, translationToChinese, translation } from "@/api/chat";
2025-08-14 19:52:34 +08:00
import { update } from '@/api/account'; //更新主播信息
2025-07-01 21:36:08 +08:00
import MultiLineInputDialog from '@/components/MultiLineInputDialog.vue'; // 根据实际路径修改
import ChatDialog from '@/components/ChatDialog.vue'
2025-08-14 19:52:34 +08:00
// import { splitArray } from '@/utils/arrUtil' //分割数组 分配主播 已废弃
2025-07-15 13:45:36 +08:00
import { chooseFile } from '@/utils/fileUtil'
2025-07-30 13:34:49 +08:00
import { connectSSE } from '@/utils/sseUtils'
import { prologue, comment } from '@/api/account';
2025-08-14 19:52:34 +08:00
import { createTaskQueue } from '@/composables/useTaskQueue' //创建任务
import { useCanvasPointer } from '@/composables/useCanvasPointer' //canvas 初始化 点击转换
import { attachTrimmerForIndex } from '@/composables/useVideoStream' //修剪器
2025-07-01 21:36:08 +08:00
const router = useRouter();
let wsActions = null;
2025-07-30 13:34:49 +08:00
let userdata = getUser();
2025-07-01 21:36:08 +08:00
// 引入刷新方法
2025-08-14 19:52:34 +08:00
// const reload = inject("reload")
2025-07-01 21:36:08 +08:00
let phone = ref({ width: 207, height: 470 });
const openStr = base64ToBinary("ZQBwAAAAAAA8CgLQAtAAAAAAAAAAAAD/AAAAAAAAAAAAAAAA"); //开启视频流的启动命令
const eitwo = base64ToBinary("BAIAAABHVFJD"); //开启设备信息的命令
2025-07-15 13:45:36 +08:00
let isshow = ref(true); //页面是否隐藏
2025-07-01 21:36:08 +08:00
let playTimer = ref([{}, {}, {}, {}, {}, {}, {}, {}]); // 播放定时器
2025-07-15 13:45:36 +08:00
let isdown = ref(false); //是否是按下鼠标状态
const videoElement = ref({}); //视频流元素DOM
let deviceInformation = ref([]); //设备信息列表
2025-07-01 21:36:08 +08:00
//当前弹窗类型
let dialogTitle = ref('');
//评论文本内容
let textContent = ref(['', '', '', '', '', '', '', '']);
let textContentArr = ref([[], [], [], [], [], [], [], [], []]);
2025-08-07 21:35:27 +08:00
//当前主播id内容
2025-07-30 13:34:49 +08:00
let hostIdArr = ref([{}, {}, {}, {}, {}, {}, {}, {}]);
// let hostIdContentArr = ref([[], [], [], [], [], [], [], [], []]);
2025-07-01 21:36:08 +08:00
//私信文本内容
let textContentpri = ref(['', '', '', '', '', '', '', '']);
let textContentpriArr = ref([[], [], [], [], [], [], [], [], []]);
//保存聊天内容
2025-07-15 13:45:36 +08:00
let chatList = ref([]);
2025-07-30 13:34:49 +08:00
//保存爬虫发送的主播信息 待存缓存
let stroageHost = ref([]);
2025-07-01 21:36:08 +08:00
//选中设备
let selectedDevice = ref(999);
//ws列表
let wslist = [];
2025-07-30 13:34:49 +08:00
// 是否停止
let isStop = ref(false);
2025-08-14 19:52:34 +08:00
2025-07-30 13:34:49 +08:00
//sse弹窗是否存在
let isMsgPop = ref(false);
2025-07-01 21:36:08 +08:00
//播放器列表
2025-08-11 22:00:45 +08:00
let instanceList = [{}, {}, {}, {}, {}, {}, {}, {}];
2025-07-01 21:36:08 +08:00
//是否是在关注主播
let runType = ref(['', '', '', '', '', '', '', '']);
//屏幕尺寸系数
2025-08-07 21:35:27 +08:00
let isMonitor = ref(false);
2025-07-01 21:36:08 +08:00
let iponeCoefficient = ref([{ width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }]);
//是否是发送的内容
let isSend = ref(false)
//弹窗是否显示
let showDialog = ref(false);
//监听消息定时器
let isShowMes = ref();
//给ws-scrcpy发送的数据格式
const mouseData = {
type: 2,
action: 0,
pointerId: 0,
position: {
point: { x: 0, y: 0 },
screenSize: { width: 320, height: 720 },
},
pressure: 1,
buttons: 1,
};
//打开聊天窗口的状态
2025-08-07 21:35:27 +08:00
let openShowChat = ref(true);
2025-07-15 13:45:36 +08:00
let istranslate = ref(false); //是否是翻译本页
2025-07-01 21:36:08 +08:00
let phoneXYinfo = ref(getphoneXYinfo() == null ? [{}, {}, {}, {}, {}, {}, {}, {}] : getphoneXYinfo());
2025-08-07 21:35:27 +08:00
// 当前悬浮的按钮索引
const hoverIndex = ref(null)
2025-08-11 22:00:45 +08:00
const isMonitorOn = ref(false) // false 表示关闭true 表示开启
2025-08-07 21:35:27 +08:00
// 你可以用这种方式声明按钮们
const buttons = [
{
label: '刷新',
2025-08-14 19:52:34 +08:00
onClick: () => reload({ onlySelected: selectedDevice.value !== 999, hard: true }),
2025-08-07 21:35:27 +08:00
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn1.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn1-1.png', import.meta.url).href
}
},
{
label: '打开tiktok',
onClick: () => openTk(),
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn2.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn2-2.png', import.meta.url).href
}
},
{
label: '重置tiktok',
onClick: () => resetTk(),
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn3.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn3-3.png', import.meta.url).href
}
},
{
label: '打开直播',
2025-08-11 22:00:45 +08:00
onClick: () => {
runType.value[0] = 'brushLive'
brushLive()
},
2025-08-07 21:35:27 +08:00
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn4.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn4-4.png', import.meta.url).href
2025-08-11 22:00:45 +08:00
},
style: () => ({
backgroundColor: runType.value[0] == 'brushLive' ? 'red' : ''
})
2025-08-07 21:35:27 +08:00
},
{
label: '一键养号',
2025-08-11 22:00:45 +08:00
onClick: () => {
if (runType.value[0] == 'like') return;
runType.value[0] = 'like'
parentNum()
},
2025-08-07 21:35:27 +08:00
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn5.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn5-5.png', import.meta.url).href
2025-08-11 22:00:45 +08:00
},
style: () => ({
backgroundColor: runType.value[0] == 'like' ? 'red' : ''
})
2025-08-07 21:35:27 +08:00
},
{
label: '一键关注并打招呼',
onClick: () => {
2025-08-11 22:00:45 +08:00
if (runType.value[0] == 'follow') return;
runType.value[0] = 'follow'
2025-08-07 21:35:27 +08:00
showDialog.value = true
dialogTitle.value = '主播ID'
selectedDevice.value = 999
},
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn6.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn6-6.png', import.meta.url).href
2025-08-11 22:00:45 +08:00
},
style: () => ({
backgroundColor: runType.value[0] == 'follow' ? 'red' : ''
})
2025-08-07 21:35:27 +08:00
},
{
2025-08-11 22:00:45 +08:00
label: '监测消息',
onClick: () => {
isMonitorOn.value = !isMonitorOn.value
if (isMonitorOn.value) {
openMonitor()
} else {
cloesMonitor()
}
},
show: () => true,
2025-08-07 21:35:27 +08:00
img: {
normal: new URL('@/assets/video/leftBtn1.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn1-1.png', import.meta.url).href
},
2025-08-11 22:00:45 +08:00
style: () => ({
backgroundColor: isMonitorOn.value ? 'red' : ''
})
2025-08-07 21:35:27 +08:00
},
{
label: '全部停止',
onClick: () => stop(),
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn8.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn8-8.png', import.meta.url).href
}
},
{
label: '登出',
onClick: () => router.push('/'),
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn9.png', import.meta.url).href,
hover: new URL('@/assets/video/leftBtn9-9.png', import.meta.url).href
}
}
]
2025-08-14 19:52:34 +08:00
// 放在变量都已声明之后(要能拿到 phone、toBuffer、wslist
const { canvasRef, frameMeta, initCanvas, getCanvasCoordinate, sendPointer } =
useCanvasPointer({
phone, // 你已有的 ref({ width, height })
toBuffer, // 你已有的工具函数
getWs: (i) => wslist[i] // 取对应 index 的 WebSocket
});
2025-08-11 22:00:45 +08:00
const feedState = Array(8).fill(null).map(() => ({
processing: false,
pending: null, // ArrayBuffer 等最新一段
}));
function pushFrame(index, buf) {
const st = feedState[index];
if (st.processing) {
// 覆盖旧的等待帧,保留最新
st.pending = buf;
return;
}
st.processing = true;
try {
//推送帧到video
instanceList[index].converter.appendRawData(new Uint8Array(buf));
} finally {
st.processing = false;
if (st.pending) {
const next = st.pending;
st.pending = null;
// 用微任务衔接,避免递归栈增长
queueMicrotask(() => pushFrame(index, next));
}
}
}
2025-07-01 21:36:08 +08:00
const wsCache = new Map();
2025-08-07 21:35:27 +08:00
2025-08-14 19:52:34 +08:00
//````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
2025-07-01 21:36:08 +08:00
// 初始化 手机显示WebSocket 和视频流
2025-08-14 19:52:34 +08:00
const initVideoStream = async (udid, index) => {
//````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
2025-07-01 21:36:08 +08:00
// 1. 检查缓存中是否已有实例
if (wsCache.has(udid)) {
2025-08-11 22:00:45 +08:00
const cached = wsCache.get(udid);
if (cached?.ws?.readyState === WebSocket.OPEN) {
return cached.ws;
2025-07-01 21:36:08 +08:00
}
// 如果连接已关闭,清除缓存并重新创建
wsCache.delete(udid);
}
2025-08-14 19:52:34 +08:00
// 等待 <video> 元素挂载
const el = await waitForVideoEl(udid);
if (!el) {
console.error('[initVideoStream] video element not ready for', udid);
return;
}
// 准备容器
instanceList[index] = { converter: null, timer: null };
//````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
2025-07-01 21:36:08 +08:00
if (!videoElement.value) return;
2025-08-14 19:52:34 +08:00
if (!videoElement.value?.[udid]) {
console.warn('video element missing for', udid);
return; // 不创建 converter避免传 null 进去
}
2025-07-01 21:36:08 +08:00
// 1. 创建 h264-converter 实例
2025-08-11 22:00:45 +08:00
instanceList[index].converter = new VideoConverter(videoElement.value[udid], 60, 1);
2025-07-01 21:36:08 +08:00
// 2. 连接 WebSocket
wslist[index] = new WebSocket(
`ws://127.0.0.1:8000/?action=proxy-adb&remote=tcp%3A8886&udid=${udid}`
);
wslist[index].binaryType = "arraybuffer";
2025-08-14 19:52:34 +08:00
attachTrimmerForIndex(instanceList, videoElement, deviceInformation, index, 10, 2000); // 挂上修剪器
2025-07-01 21:36:08 +08:00
wslist[index].onopen = () => {
console.log("手机显示ws已开启");
2025-07-30 13:34:49 +08:00
wsActions = createWsActions(wslist);
2025-07-01 21:36:08 +08:00
// 发送 开启 视频流数据
setTimeout(() => {
wslist[index].send(openStr);
}, 300);
2025-08-11 22:00:45 +08:00
wsCache.set(udid, { ws: wslist[index], index });
2025-07-01 21:36:08 +08:00
};
const magicSize = stringToUtf8ByteArray('scrcpy_message');
// 3. 处理接收到的二进制数据
wslist[index].onmessage = (event) => {
2025-08-11 22:00:45 +08:00
2025-07-01 21:36:08 +08:00
//判断返回的如果是字符串为自定义返回
if (typeof event.data == 'string') {
2025-07-30 13:34:49 +08:00
if (isStop.value) {
2025-07-01 21:36:08 +08:00
createTaskQueue(index).clear();//清除队伍中的任务
return;
}
const resData = JSON.parse(event.data)
//成功处理
console.log('自定义返回', resData)
if (resData.status === 'success') {
//触发长按该位置
if (resData.type == 'clickCopy') {
console.log('长按', resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index)
clickxy(resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index, 9) //index为9的时候长按
setTimeout(() => {
wsActions.clickCopyText(resData.device, index)
2025-07-01 21:36:08 +08:00
}, 1500);
} else if (resData.type == 'getmesNum') {
if (resData.message == 0) {
console.log('没有消息')
2025-07-30 13:34:49 +08:00
if (runType.value[index] == 'follow') {
LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}
//如果检测到有新消息会收到两条ws回复一条message==1 一条message==成功
2025-07-01 21:36:08 +08:00
} else if (resData.message == 1) {
console.log('有消息')
} else if (resData.message == '点击成功') {
console.log('双击', iponeCoefficient.value[index].width, iponeCoefficient.value[index].height)
setTimeout(() => {
clickxy(resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index)
setTimeout(() => {
clickxy(resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index) //index为9的时候长按
}, 100)
2025-07-30 13:34:49 +08:00
wsActions.clickSysMesage(deviceInformation.value[index].udid, index) //点击消息进入对话框
2025-07-01 21:36:08 +08:00
}, 1500)
}
} else if (resData.type == 'clickMesage') {
2025-07-30 13:34:49 +08:00
//点击进入新消息页面以后,获取页面信息
2025-07-01 21:36:08 +08:00
wsActions.clickCopyList(deviceInformation.value[index].udid, index)
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'clickSysMesage') {
setTimeout(() => {
Back('', index)
setTimeout(() => {
Back('', index)
}, 1000)
2025-08-07 21:35:27 +08:00
}, 1000)
2025-07-01 21:36:08 +08:00
} else if (resData.type == 'isVideoAndLive') {
console.log(resData.message)
} else if (resData.type == 'clickCopyList') { //获取到的消息列表
//打印所有信息
console.log('消息列表', resData.message)
2025-07-15 13:45:36 +08:00
chatList.value = resData.message
getTranslation(chatList.value)
2025-07-01 21:36:08 +08:00
const mesBox = resData.message[resData.message.length - 1]
//打印最新一条信息
console.log('最新消息', mesBox)
2025-07-15 13:45:36 +08:00
console.log("翻译", istranslate.value)
if (istranslate.value == false) {
2025-08-11 22:00:45 +08:00
if (mesBox.position == 'right') {
Back('', index)
setTimeout(() => {
Back('', index)
}, 1000)
return
}
2025-07-30 13:34:49 +08:00
openShowChat.value = true
2025-07-15 13:45:36 +08:00
console.log("执行ai")
2025-08-07 21:35:27 +08:00
2025-07-15 13:45:36 +08:00
chat({ msg: mesBox.text }).then(res => {
console.log("ai返回", res)
2025-07-30 13:34:49 +08:00
textContentpri.value[index] = res.result
2025-07-15 13:45:36 +08:00
PrivatetexToPrivatePush(deviceInformation.value[index].udid, index)
})
2025-08-07 21:35:27 +08:00
2025-07-15 13:45:36 +08:00
} else {
console.log("翻译本页")
istranslate.value = false
}
2025-07-01 21:36:08 +08:00
} else if (resData.action == 'getSize') {
// console.log(iponeCoefficient.value, '手机尺寸宽度:', resData.width, '高度:', resData.height);
const RAW_W = Math.max(1, resData.width || 0);
const RAW_H = Math.max(1, resData.height || 1);
// 目标高度固定 720按比例缩放宽
const TARGET_H = 720;
const ratio = TARGET_H / RAW_H;
const scaledWFloat = RAW_W * ratio;
const scaledH = TARGET_H;
// 只向下对齐floor。要 32 对齐就把 ALIGN 改 32
const ALIGN = 16;
const downAlign = (x, a) => Math.max(a, Math.floor(x / a) * a);
const scaledW = downAlign(scaledWFloat, ALIGN);
// 1) 给后续发送 pointer 用的 screenSize服务端就按这个坐标系解释 point
mouseData.position.screenSize.width = scaledW;
mouseData.position.screenSize.height = scaledH;
// 2) 保存“对齐后的尺寸”,用于坐标换算等
frameMeta.value[resData.device] = {
w: scaledW,
h: scaledH,
rotation: 0,
};
// 3) 你代码里很多地方用 resData.x * iponeCoefficient把它同步到 320×720 逻辑坐标
// 这样 UI 节点坐标(原始帧坐标) -> 320×720 逻辑坐标
// iponeCoefficient.value[index].width = 320 / scaledW;
// iponeCoefficient.value[index].height = 720 / scaledH;
iponeCoefficient.value[index].width = scaledW / resData.width
iponeCoefficient.value[index].height = scaledH / resData.height
console.log(
`[getSize] raw=${RAW_W}x${RAW_H} -> scaled=${scaledW}x${scaledH} (align↓${ALIGN})`
);
2025-07-01 21:36:08 +08:00
} else {
console.log(resData.type, '坐标返回x:', resData.x, 'y:', resData.y);
}
2025-08-04 13:07:36 +08:00
phoneXYinfo.value[index].id = resData.device
2025-07-01 21:36:08 +08:00
if (resData.type == 'Likes') {//判断是否是 点赞
phoneXYinfo.value[index].Likes = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
if (runType.value[index] == 'follow') {
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
}
} else if (resData.type == 'Comment') {//打开评论
phoneXYinfo.value[index].Comment = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'CommentText') {//复制评论
phoneXYinfo.value[index].CommentText = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
textContent.value[index] = resData.message
2025-07-01 21:36:08 +08:00
} else if (resData.type == 'Comtext') {//评论输入
phoneXYinfo.value[index].Comtext = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'ComPush') {//评论发送
phoneXYinfo.value[index].ComPush = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
setTimeout(() => {
Back('', index)
if (runType.value[index] == 'follow') {
setTimeout(() => {
Back('', index)
}, 1000)
} else {
setTimeout(() => {
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
setTimeout(() => {
console.log('观看视频中')
randomSeeVideo(deviceInformation.value[index].udid, index) //50秒后继续循环任务
}, 1000);
}, 1000)
}
}, 800)
} else if (resData.type == 'tomy') {//打开主页
phoneXYinfo.value[index].tomy = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'Attention') {//关注、打开私信
phoneXYinfo.value[index].Attention = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
// LikesToCommentToComPush(deviceInformation.value[index].udid, index) //是否继续循环任务
} else if (resData.type == 'return') {//私信评论
phoneXYinfo.value[index].return = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'addHost') {//添加关注
phoneXYinfo.value[index].addHost = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
setTimeout(() => {
wsActions.isHost(deviceInformation.value[index].udid, index)//检测
}, 1000);
} else if (resData.type == 'Privatetex') {//私信评论
phoneXYinfo.value[index].Privatetex = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'PrivatePush' || resData.type == 'PrivatePushFollow') {//私信发送
2025-07-01 21:36:08 +08:00
phoneXYinfo.value[index].PrivatePush = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
setTimeout(() => {
Back('', index);
setTimeout(() => {
Back('', index);
2025-07-30 13:34:49 +08:00
setTimeout(() => {
Back('', index);
setTimeout(() => {
if (resData.type == 'PrivatePush') {
Back('', index);
setTimeout(() => {
Back('', index);
setTimeout(() => {
Back('', index);
2025-08-07 21:35:27 +08:00
if (isMonitor.value) {
//正常没有消息,发送完私信以后,返回六次,然后继续下一个任务
wsActions.getmesNum(deviceInformation.value[index].udid, index)
} else {
LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}
2025-07-30 13:34:49 +08:00
}, 1000);
}, 1000);
} else if (resData.type == 'PrivatePushFollow') {
//如果有新消息,回复完私信以后,返回三次,然后继续下一个任务
wsActions.getmesNum(deviceInformation.value[index].udid, index)
// LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}
}, 1000);
}, 1000);
2025-07-01 21:36:08 +08:00
}, 1000);
}, 1000);
2025-07-30 13:34:49 +08:00
2025-07-01 21:36:08 +08:00
} else if (resData.type == 'clickCopy') {//点击复制
phoneXYinfo.value[index].clickCopy = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'hostVideo') {//观看主播视频
2025-07-01 21:36:08 +08:00
phoneXYinfo.value[index].hostVideo = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'isHost') {//视频关注主播
phoneXYinfo.value[index].isHost = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
if (resData.message == 0) {
console.log('无关注', index)
Back(deviceInformation.value[index].udid, index)
setTimeout(() => {
console.log('观看视频中', index)
randomSeeVideo(deviceInformation.value[index].udid, index) //30-50秒后继续循环任务
}, 1000);
} else if (resData.message == 1) {
console.log('有关注', index)
const randomNum = Math.random(); // 生成一个0到1之间的随机数
2025-08-04 13:07:36 +08:00
//是否点赞评论的概率
if (randomNum < 0.07) {
2025-07-01 21:36:08 +08:00
console.log('进行点赞评论', index)
LikesToCommentToComPush(deviceInformation.value[index].udid, index)
} else {
console.log('下一个', index)
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
2025-07-30 13:34:49 +08:00
randomSeeVideo(deviceInformation.value[index].udid, index) //30-50秒后继续循环任务
2025-07-01 21:36:08 +08:00
}
}
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'openDY') {//视频关注主播
createTaskQueue(index).clear();//清除队伍中的任务
2025-08-04 13:07:36 +08:00
} else if (resData.type == 'toLive') {//打开直播
wsActions.isHead(deviceInformation.value[index].udid, index)//判断直播
} else if (resData.type == 'isHead') {//判断有没有头像
if (resData.message == 1) {
wsActions.isOneLive(deviceInformation.value[index].udid, index)//判断直播
} else {
return
}
} else if (resData.type == 'isOneLive') {//判断单人还是双人
if (resData.message == 0) {
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
wsActions.isHead(deviceInformation.value[index].udid, index)//判断直播
return
}
// 用法:
(async () => {
const result = await randomDelayAndExecute();
if (runType.value[0] !== 'brushLive') {
return
}
2025-08-04 13:07:36 +08:00
//30%概率
if (result) {
let count = 0;
const num = Math.floor(Math.random() * 31) + 5;
const interval = setInterval(() => {
if (count >= num) {
clearInterval(interval);
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
wsActions.isHead(deviceInformation.value[index].udid, index)//判断直播
return;
}
clickxy(
resData.x * iponeCoefficient.value[index].width,
resData.y * iponeCoefficient.value[index].height,
index
)
count++;
}, 300);
return;
} else {
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
wsActions.isHead(deviceInformation.value[index].udid, index)//判断直播
return
}
})();
2025-07-01 21:36:08 +08:00
}
2025-08-04 13:07:36 +08:00
2025-07-01 21:36:08 +08:00
setphoneXYinfo(phoneXYinfo.value)
if (resData.type != 'isHost') {
if (resData.type == 'hostVideo' || resData.type == 'Likes') {
setTimeout(() => {
createTaskQueue(index).next(); // 继续队列中下一个任务
}, 5000)
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'getmesNum') {
2025-07-01 21:36:08 +08:00
} else {
createTaskQueue(index).next(); // 继续队列中下一个任务
}
}
} else {
2025-07-30 13:34:49 +08:00
// ----------------------------------------------------------------------------------------------------报错处理
2025-08-07 21:35:27 +08:00
//如果该视频无法被评论,或者没有评论,返回刷下一条视频
2025-07-30 13:34:49 +08:00
if (resData.type == 'Comtext' || resData.type == 'CommentText') {
2025-07-01 21:36:08 +08:00
if (runType.value[index] == 'follow') {
2025-07-30 13:34:49 +08:00
2025-07-01 21:36:08 +08:00
Back('', index)
setTimeout(() => {
Back('', index)
2025-07-30 13:34:49 +08:00
createTaskQueue(index).next(); // 继续队列中下一个任务
createTaskQueue(index).next(); // 继续队列中下一个任务
2025-07-01 21:36:08 +08:00
}, 1000)
} else {
2025-07-30 13:34:49 +08:00
resetApp(udid, index)
2025-07-01 21:36:08 +08:00
setTimeout(() => {
2025-07-30 13:34:49 +08:00
wsActions.isHost(deviceInformation.value[index].udid, index)
2025-07-01 21:36:08 +08:00
}, 1000)
}
2025-07-30 13:34:49 +08:00
//如果该视频无法被搜索到,返回刷下一条视频
} else if (resData.type == 'toHost') {
2025-07-01 21:36:08 +08:00
Back('', index)
setTimeout(() => {
Back('', index)
2025-07-30 13:34:49 +08:00
createTaskQueue(index).clear();//清除队伍中的任务
setTimeout(() => {
Back('', index)
LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}, 1000)
2025-07-01 21:36:08 +08:00
}, 1000)
2025-08-07 21:35:27 +08:00
} else if (resData.type == 'Privatetex' || resData.type == 'hostVideo' || resData.type == 'search' || resData.type == 'Attention' || resData.type == 'Comment') {
2025-07-30 13:34:49 +08:00
if (runType.value[index] == 'follow') {
//关注的时候出现无法私信和没有视频的情况 错误重置
resetApp(udid, index)
setTimeout(() => {
wsActions.getmesNum(deviceInformation.value[index].udid, index)
// LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}, 1000)
}
2025-07-01 21:36:08 +08:00
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'getmesNum') {
if (runType.value[index] == 'follow') {
LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}
//getmesNum出现没有消息的情况
} else if (resData.type == 'clickMesage' || resData.type == 'ComPush') {
//ComPush关注主播的时候出现无法评论的视频 会跳过评论发送,不处理评论发送的返回错误
2025-07-01 21:36:08 +08:00
} else if (resData.type == 'clickCopyList') {
//如果无法获取到聊天记录,返回继续检测
2025-07-30 13:34:49 +08:00
console.error('未找到聊天,返回')
2025-07-01 21:36:08 +08:00
Back('', index)
2025-07-30 13:34:49 +08:00
} else if (resData.type == 'clickSysMesage') {
//如果没有系统消息,则点击新消息
wsActions.clickMesage(deviceInformation.value[index].udid, index) //点击消息进入对话框
2025-07-01 21:36:08 +08:00
} else {
2025-07-30 13:34:49 +08:00
console.error(resData.message, resData.type); // 错误处理
2025-07-01 21:36:08 +08:00
ElMessage.error(resData.message);
createTaskQueue(index).clear();//清除队伍中的任务
}
// createTaskQueue(index).next(); // 继续队列中下一个任务
}
2025-08-11 22:00:45 +08:00
} else {
const buf = event.data; // ArrayBuffer
const view = new Uint8Array(buf); // 只创建视图,不复制
// 粘贴板消息头判断FIX: 用 view别用未定义的 data
if (startsWithHeader(magicSize, view)) {
if (!isSend.value) {
const payload = trimLongArray(view, magicSize);
const paste = bufferToString(payload);
console.log('获取粘贴板内容', paste);
}
return; // 这类消息不走视频通道
2025-07-01 21:36:08 +08:00
}
2025-08-11 22:00:45 +08:00
// 视频流
if (instanceList[index].converter && isshow.value) {
2025-08-14 19:52:34 +08:00
pushFrame(index, buf); //处理视频流帧
2025-07-01 21:36:08 +08:00
}
}
2025-08-11 22:00:45 +08:00
2025-07-01 21:36:08 +08:00
};
// 4. 错误处理
wslist[index].onerror = (error) => {
wsCache.delete(udid); // 错误时清理缓存
};
//``````````````````````````````````````````````````````````````````````````````````
wslist[index].onclose = (event) => {
wsCache.delete(udid)// 自动清理缓存
2025-08-11 22:00:45 +08:00
clearInterval(instanceList[index].timer); // 清理定时器// 移除缓存
2025-07-01 21:36:08 +08:00
};
//``````````````````````````````````````````````````````````````````````````````````
};
2025-08-14 19:52:34 +08:00
2025-07-01 21:36:08 +08:00
// 鼠标按下事件处理
const handleCanvasdown = (udid, e, index) => {
const { x, y } = getCanvasCoordinate(e, udid);
2025-07-01 21:36:08 +08:00
isdown.value = true;
sendPointer(udid, index, 0, x, y);
setTimeout(() => { selectedDevice.value = index; }, 300);
2025-07-01 21:36:08 +08:00
};
// 鼠标抬起事件处理
const handleCanvasup = (udid, e, index) => {
const { x, y } = getCanvasCoordinate(e, udid);
2025-07-01 21:36:08 +08:00
isdown.value = false;
sendPointer(udid, index, 1, x, y);
2025-07-01 21:36:08 +08:00
};
// 鼠标移动事件处理
const handleMouseMove = (udid, e, index) => {
if (!isdown.value) return;
const { x, y } = getCanvasCoordinate(e, udid);
sendPointer(udid, index, 2, x, y);
2025-07-01 21:36:08 +08:00
};
2025-07-01 21:36:08 +08:00
// ======= 对齐工具 =======
const ALIGN_BASE = 16; // 改成 32 就是 32 对齐
const ROUND_MODE = 'nearest'; // 'down' | 'up' | 'nearest'
function alignTo(x, a = ALIGN_BASE, mode = ROUND_MODE) {
if (a <= 1) return Math.round(x);
const q = x / a;
if (mode === 'down') return Math.floor(q) * a;
if (mode === 'up') return Math.ceil(q) * a;
/* nearest */ return Math.round(q) * a;
}
2025-07-01 21:36:08 +08:00
//返回上一层
const Back = (udid, index) => {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": 4, "metaState": 0, "repeat": 0 }));
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": 4, "metaState": 0, "repeat": 0 }));
}
//返回首页
const Home = (udid, index) => {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": 3, "metaState": 0, "repeat": 0 }));
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": 3, "metaState": 0, "repeat": 0 }));
}
//任务视图
const Overview = (udid, index) => {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": 187, "metaState": 0, "repeat": 0 }));
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": 187, "metaState": 0, "repeat": 0 }));
}
//回车 index手机下标 state up按下 down抬起 keycode 键
const key = (index, state, keycode) => {
// console.log("退格", index)
if (index === 999) {
return
} else {
if (state == 'up') {
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": keycode, "metaState": 0, "repeat": 0 }));
} else if (state == 'down') {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": keycode, "metaState": 0, "repeat": 0 }));
}
}
}
const handleVisibilityChange = () => {
if (document.visibilityState === "hidden") {
console.log("页面被隐藏");
isshow.value = false;
// 页面被隐藏时执行的逻辑
} else if (document.visibilityState === "visible") {
console.log("页面变为可见");
setTimeout(() => {
isshow.value = true;
// ObtainDeviceInformation();
}, 500);
}
};
onMounted(() => {
2025-08-13 15:27:15 +08:00
// document.addEventListener("visibilitychange", handleVisibilityChange);
2025-07-01 21:36:08 +08:00
ObtainDeviceInformation();
2025-08-13 15:27:15 +08:00
// window.addEventListener('keydown', (e) => {
// // console.log('触发按键了 键盘事件有效', e)
// if (e.key == 'Backspace') {
// key(selectedDevice.value, 'down', 67)
// }
// })
// window.addEventListener('keyup', (e) => {
// // console.log('触发按键了 键盘事件有效抬起', e.keyCode, e.key)
// if (e.key == 'Backspace') {
// key(selectedDevice.value, 'up', 67)
// }
// })
2025-07-01 21:36:08 +08:00
const loading = ElLoading.service({
lock: true,
text: '初始化中...',
background: 'rgba(0, 0, 0, 0.7)',
})
setTimeout(() => {
loading.close()
}, 2000)
2025-07-30 13:34:49 +08:00
//sse接收爬虫发送的消息
connectSSE(`https://datasave.api.yolozs.com/api/sse/connect/${userdata.tenantId}/${userdata.id}`, (data) => {
// connectSSE(`http://192.168.1.155:19665/api/sse/connect/${userdata.tenantId}/${userdata.id}`, (data) => {
// 处理服务端推送的数据
console.log('来自服务端:', data)
//接收到start
if (data === 'start') {
if (!isMsgPop.value) {
isMsgPop.value = true;
2025-08-07 21:35:27 +08:00
2025-07-30 13:34:49 +08:00
ElMessageBox.confirm(
'检测到YOLO助手正在爬取主播是否进行操作',
'消息提醒',
{
confirmButtonText: '开始',
cancelButtonText: '取消',
type: 'success',
}
)
.then(() => {
//开启sse版follow任务
ElMessage({
type: 'success',
message: '任务开启成功',
})
setHostList(stroageHost.value)
//重启tk
resetTk()
//获取评论
comment().then((resA) => {
console.log('resA:', resA);
setContentList(resA)
//评论
textContentArr.value.forEach((item, indexA) => {
textContent.value[indexA] = resA[getRandomNumber(resA.length - 1)];
})
prologue().then((resB) => {
console.log('resB:', resB);
setContentpriList(resB)
//私信
textContentpriArr.value.forEach((item, indexA) => {
textContentpri.value[indexA] = resB[getRandomNumber(resB.length - 1)];
runType.value[indexA] = 'follow'
console.log('runType', runType.value[indexA])
})
deviceInformation.value.forEach((device, indexB) => {
if (getHostList().length <= 0) return;
// LikesToLikesToLikes(device.udid, indexB)
wsActions.getmesNum(device.udid, indexB)
})
})
})
})
.catch(() => {
stroageHost.value = []
isMsgPop.value = false;
})
}
} else {
2025-08-07 21:35:27 +08:00
// stroageHost.value = getHostList()
2025-07-30 13:34:49 +08:00
stroageHost.value.push(({ country: data.country, text: data.hostsId, state: false }))
2025-08-07 21:35:27 +08:00
if (runType.value[0] == 'follow') {
2025-08-04 13:07:36 +08:00
setHostList(stroageHost.value)
}
2025-07-30 13:34:49 +08:00
2025-08-04 13:07:36 +08:00
}
2025-07-30 13:34:49 +08:00
})
2025-07-01 21:36:08 +08:00
});
2025-08-07 21:35:27 +08:00
//更新状态
// update(
// {
// id: data.id,
// operationStatus: 1,
// }
// ).then(() => {
2025-07-01 21:36:08 +08:00
2025-08-07 21:35:27 +08:00
// })
2025-07-30 13:34:49 +08:00
2025-07-01 21:36:08 +08:00
onBeforeUnmount(() => {
2025-08-13 15:27:15 +08:00
// document.removeEventListener("visibilitychange", handleVisibilityChange);
2025-07-01 21:36:08 +08:00
});
// 组件卸载时清理
onUnmounted(() => {
console.log("卸载组件");
cloesMonitor(); //关闭检测消息
});
//获取设备信息
const ObtainDeviceInformation = () => {
// 2. 连接 WebSocket
const ws = new WebSocket("ws://127.0.0.1:8000/?action=multiplex");
ws.binaryType = "arraybuffer";
ws.onopen = () => {
ws.send(eitwo);
};
// 3. 处理接收到的二进制数据
2025-08-14 19:52:34 +08:00
ws.onmessage = async (event) => {
2025-07-01 21:36:08 +08:00
const data = JSON.parse(new TextDecoder('utf-8').decode(event.data).replace(/[^\x20-\x7F]/g, ''));
try {
console.log('数组', data)
if (data.type == "devicelist") {
deviceInformation.value = [];
const filteredList = data.data.list.filter(item => item.state === 'device');
//检测到设备列表时,渲染所有设备
2025-08-14 19:52:34 +08:00
for (const item of filteredList) {
deviceInformation.value.push(item);
await nextTick(); // 等 v-for 渲染出 <video>
initCanvas(item.udid); // 如果它也依赖 DOM同样要在 nextTick 之后
initVideoStream(item.udid, deviceInformation.value.length - 1);
// getSize 建议放到 wslist[index].onopen 里最稳,
// 若保留延时也可以:
setTimeout(() => wsActions?.getSize(item.udid, deviceInformation.value.length - 1), 2000);
}
2025-07-01 21:36:08 +08:00
} else if (data.type == "device") {
if (data.data.device.state === "offline") {
//监听设备信息,出现离线设备,则删除设备信息
deviceInformation.value.forEach((item, index) => {
if (item.udid === data.data.device.udid) {
deviceInformation.value.splice(index, 1);
if (index < wslist.length - 1) {
deviceInformation.value.forEach((item, index1) => {
//关闭websocket连接
wslist.forEach((item, index) => {
2025-08-14 19:52:34 +08:00
new VideoConvertitem.close();
2025-07-01 21:36:08 +08:00
})
//重新连接websocket
new Promise((resolve, reject) => {
setTimeout(() => {
try {
initVideoStream(item.udid, index1);
initCanvas(item.udid);
} catch (error) {
reject(error);
}
}, 300);
});
})
}
console.log("deviceInformation", deviceInformation.value);
}
})
} else if (data.data.device.state === "device") {
let isrepeat = false;
//监听设备信息,出现新设备,则添加设备信息
deviceInformation.value.forEach((item, index) => {
if (item.udid == data.data.device.udid) {
isrepeat = true;
}
})
if (!isrepeat) {
deviceInformation.value.push(data.data.device);
new Promise((resolve, reject) => {
setTimeout(() => {
try {
initVideoStream(data.data.device.udid, deviceInformation.value.length - 1);
initCanvas(data.data.device.udid);
} catch (error) {
// reject(error);
}
}, 1000);
});
}
};
}
} catch (e) {
console.error(e);
}
};
}
//喜欢-输入评论
async function LikesToCommentToComPush(udid, index) {
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Comment', index, resourceId: 'com.zhiliaoapp.musically:id/cvd' });
2025-07-30 13:34:49 +08:00
await sendWsTask(index, { udid, action: 'dump', type: 'CommentText', index, resourceId: 'com.zhiliaoapp.musically:id/d5q' });
2025-07-01 21:36:08 +08:00
await sendWsTask(index, { udid, action: 'click', type: 'Comtext', index, resourceId: 'com.zhiliaoapp.musically:id/cs0' });
await sendWsTask(index, { udid, action: 'click', type: 'ComPush', index, resourceId: 'com.zhiliaoapp.musically:id/bqg' });
}
//点赞主页4个作品+评论最后一个作品并返回
2025-07-30 13:34:49 +08:00
async function LikesToLikesToLikes(udid, index) {
isStop.value = false;
2025-07-01 21:36:08 +08:00
runType.value[index] = 'follow';
2025-07-30 13:34:49 +08:00
// await sendWsTask(index, { udid, action: 'click', type: 'getmesNum', index, resourceId: 'com.zhiliaoapp.musically:id/jyv' });
await sendWsTask(index, { udid, action: 'click', type: 'search', index, resourceId: 'com.zhiliaoapp.musically:id/gtz' });
2025-07-01 21:36:08 +08:00
await sendWsTask(index, { udid, action: 'click', type: 'searchHost', index, resourceId: 'com.zhiliaoapp.musically:id/t6f' });
await sendWsTask(index, { udid, action: 'click', type: 'toHost', index, resourceId: 'com.zhiliaoapp.musically:id/iso' });
await sendWsTask(index, { udid, action: 'click', type: 'hostVideo', index, resourceId: 'com.zhiliaoapp.musically:id/u3o', num: 0 });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Comment', index, resourceId: 'com.zhiliaoapp.musically:id/cvd' });
await sendWsTask(index, { udid, action: 'click', type: 'Comtext', index, resourceId: 'com.zhiliaoapp.musically:id/cs0' });
await sendWsTask(index, { udid, action: 'click', type: 'ComPush', index, resourceId: 'com.zhiliaoapp.musically:id/bqg' });
await sendWsTask(index, { udid, action: 'click', type: 'Attention', index, resourceId: 'com.zhiliaoapp.musically:id/dhx' });
await sendWsTask(index, { udid, action: 'click', type: 'Attention', index, resourceId: 'com.zhiliaoapp.musically:id/dhx' });
await sendWsTask(index, { udid, action: 'click', type: 'Privatetex', index, resourceId: 'com.zhiliaoapp.musically:id/hob' });
await sendWsTask(index, { udid, action: 'click', type: 'PrivatePush', index, resourceId: 'com.zhiliaoapp.musically:id/hog' });
// await sendWsTask(index, { udid, action: 'click', type: 'fanhui', index, resourceId: 'com.zhiliaoapp.musically:id/awi' });
}
//私信
async function PrivatetexToPrivatePush(udid, index) {
await sendWsTask(index, { udid, action: 'click', type: 'Privatetex', index, resourceId: 'com.zhiliaoapp.musically:id/hob' });
2025-07-30 13:34:49 +08:00
await sendWsTask(index, { udid, action: 'click', type: 'PrivatePushFollow', index, resourceId: 'com.zhiliaoapp.musically:id/hog' });
2025-07-01 21:36:08 +08:00
}
2025-07-30 13:34:49 +08:00
//重置当前应用
async function resetApp(udid, index) {
createTaskQueue(index).clear();//清除队伍中的任务
await sendWsTask(index, { udid, action: 'killNow' });
await sendWsTask(index, { udid, action: 'openDY' });
}
2025-07-01 21:36:08 +08:00
//发送评论字符到手机方法
function setComText(index) {
isSend.value = true;
setTimeout(() => {
isSend.value = false;
}, 300);
console.log('发送评论内容', index, textContent.value[index])
wslist[index].send(setClipboard(textContent.value[index]));
2025-07-30 13:34:49 +08:00
if (runType.value[index] === 'follow') {
textContent.value[index] = getContentList()[index][getRandomNumber(getContentList()[index].length - 1)];
}
2025-07-01 21:36:08 +08:00
}
//发送主播ID字符到手机方法
function setHostId(index) {
isSend.value = true;
setTimeout(() => {
isSend.value = false;
}, 300);
2025-07-30 13:34:49 +08:00
const host = markFirstFalseAsTrue(getHostList())
if (host == null) {
createTaskQueue(index).clear();//清除队伍中的任务
ElMessage.success('主播ID已全部发送完毕');
return;
2025-07-01 21:36:08 +08:00
}
2025-07-30 13:34:49 +08:00
hostIdArr.value[index] = host;
wslist[index].send(setClipboard(host.text)); //发送内容
2025-07-01 21:36:08 +08:00
}
2025-07-30 13:34:49 +08:00
2025-07-01 21:36:08 +08:00
//发送私信字符到手机方法
2025-07-30 13:34:49 +08:00
function setPrivateText(index, datatype) {
2025-07-01 21:36:08 +08:00
isSend.value = true;
setTimeout(() => {
isSend.value = false;
}, 300);
console.log('发送私信内容', index, textContentpri.value[index])
2025-07-30 13:34:49 +08:00
//如果是在关注主播中,发送的开场白进行对应国家翻译
if (runType.value[index] === 'follow' && datatype == 'PrivatePush') {
translation({ msg: getContentpriList()[index][getRandomNumber(getContentpriList()[index].length - 1)], country: hostIdArr.value[index].country }).then(res => {
console.log(res)
textContentpri.value[index] = res;
wslist[index].send(setClipboard(textContentpri.value[index]));
})
} else {
wslist[index].send(setClipboard(textContentpri.value[index]));
if (getContentpriList()[index]) {
textContentpri.value[index] = getContentpriList()[index][getRandomNumber(getContentpriList()[index].length - 1)];
}
2025-07-30 13:34:49 +08:00
}
2025-07-01 21:36:08 +08:00
}
//获取手机粘贴板方法
function getText(index) {
wslist[index].send(getClipboard());
}
function getComArr(index, type) {
if (type == '评论') {
2025-07-30 13:34:49 +08:00
ElMessageBox.alert(getContentList()[index], `当前${type}内容`, {
2025-07-01 21:36:08 +08:00
confirmButtonText: 'OK',
})
} else {
2025-07-30 13:34:49 +08:00
ElMessageBox.alert(getContentpriList()[index], `当前${type}内容`, {
2025-07-01 21:36:08 +08:00
confirmButtonText: 'OK',
})
}
}
// 小工具
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function tapAt(udid, index, x, y, holdMs = 80) {
sendPointer(udid, index, 0, x, y); // down
await sleep(holdMs);
sendPointer(udid, index, 1, x, y); // up
}
async function longPressAt(udid, index, x, y, holdMs = 1500) {
sendPointer(udid, index, 0, x, y); // down
await sleep(holdMs);
sendPointer(udid, index, 1, x, y); // up
}
/** 可用于自定义滑动/拖拽(如果以后要替代 slideDown/slideRight */
async function drag(udid, index, x1, y1, x2, y2, durationMs = 300, steps = 8) {
sendPointer(udid, index, 0, x1, y1); // down
const dx = (x2 - x1) / steps;
const dy = (y2 - y1) / steps;
const dt = Math.max(8, Math.floor(durationMs / steps));
for (let i = 1; i <= steps; i++) {
await sleep(dt);
sendPointer(udid, index, 2, Math.round(x1 + dx * i), Math.round(y1 + dy * i)); // move
}
await sleep(20);
sendPointer(udid, index, 1, x2, y2); // up
}
// 用 pointer down/up/move 改写后的 clickxy
async function clickxy(x, y, index, type) {
const udid = deviceInformation.value[index]?.udid;
if (!udid) return;
try {
if (type === 3) {
// 关注/私信后的返回和下滑
await tapAt(udid, index, x, y, 80);
await sleep(300);
Back('', index);
await sleep(300);
wsActions.slideDown(phoneXYinfo.value[index].id, index);
// 你原代码里这里还有 start(...),如果没定义会报错;需要的话保留:
// await sleep(300);
// start(phoneXYinfo.value[index].id, index);
return;
}
if (type === 2) {
// 评论后的返回和右滑
await tapAt(udid, index, x, y, 80);
await sleep(300);
Back('', index);
await sleep(300);
wsActions.slideRight(phoneXYinfo.value[index].id, index);
return;
}
if (type === 1) {
// 评论框点击 -> 粘贴发送
await tapAt(udid, index, x, y, 80);
await sleep(300);
setComText(index);
return;
}
if (type === 9) {
// 长按
await longPressAt(udid, index, x, y, 1500);
return;
}
// 默认:普通轻点
await tapAt(udid, index, x, y, 80);
} catch (e) {
console.error('clickxy error:', e);
2025-07-01 21:36:08 +08:00
}
}
2025-08-14 19:52:34 +08:00
// 清空喂帧状态(避免旧帧冲突)
function resetFeedState(index) {
const st = feedState[index];
if (!st) return;
st.processing = false;
st.pending = null;
}
const reload = (opts = {}) => {
const { onlySelected = false, hard = false } = opts;
const targets = (onlySelected && selectedDevice.value !== 999)
? [selectedDevice.value]
: deviceInformation.value.map((_, i) => i);
targets.forEach(i => refreshStream(i, hard));
2025-08-14 19:58:49 +08:00
ElMessage.success(`已刷新${onlySelected ? '当前设备' : '全部设备'}`);
2025-08-14 19:52:34 +08:00
};
/** 重建某台设备的视频解码器,不动 ws、不动 canvas */
function refreshStream(index, hard = false) {
const dev = deviceInformation.value[index];
if (!dev) return;
const udid = dev.udid;
const video = videoElement.value && videoElement.value[udid];
if (!video || !instanceList[index]) return;
// 1) 停止旧的喂帧状态,销毁旧 converter
resetFeedState(index);
try {
const conv = instanceList[index].converter;
if (conv && typeof conv.destroy === 'function') conv.destroy();
} catch (e) { }
instanceList[index].converter = null;
// 2) 可选“硬刷新”:彻底重置 <video>,规避 SourceBuffer 残留
if (hard) {
try { video.pause && video.pause(); } catch (e) { }
try { video.removeAttribute && video.removeAttribute('src'); } catch (e) { }
try { video.load && video.load(); } catch (e) { }
2025-07-01 21:36:08 +08:00
}
2025-08-14 19:52:34 +08:00
// 3) 新建 converter 挂到同一个 <video>
instanceList[index].converter = new VideoConverter(video, 60, 1);
// 4) 让后端立刻推关键帧/重开编码
try { wslist[index] && wslist[index].send(openStr); } catch (e) { }
// 5) 同步尺寸(不影响已有 canvas 坐标换算)
setTimeout(() => {
if (wsActions && typeof wsActions.getSize === 'function') {
wsActions.getSize(udid, index);
2025-07-01 21:36:08 +08:00
}
2025-08-14 19:52:34 +08:00
}, 300);
2025-07-01 21:36:08 +08:00
}
2025-08-14 19:52:34 +08:00
//发送任务前的处理
2025-07-01 21:36:08 +08:00
function sendWsTask(index, data) {
2025-07-30 13:34:49 +08:00
console.log('任务等待中', data.type);
2025-07-01 21:36:08 +08:00
return new Promise((resolve) => {
2025-07-30 13:34:49 +08:00
try {
const queue = createTaskQueue(index);
// console.log("创建队列", queue.getNum());
const task = () => {
//发送评论的文本粘贴事件
if (data.type == 'Likes') {
2025-07-01 21:36:08 +08:00
clickxy(160, 360, index)
}
2025-07-30 13:34:49 +08:00
if (data.type == 'isHost') {
}
if (data.type == 'Comment') {
if (runType.value[index] == 'follow') {
clickxy(160, 360, index)
}
}
if (data.type == 'ComPush') {
setTimeout(() => {
setComText(index)//粘贴内容
}, 500)
}
if (data.type == 'searchHost') {
setTimeout(() => {
setHostId(index)//粘贴内容
}, 500)
}
//发送私信的文本粘贴事件
if (data.type == 'PrivatePush' || data.type == 'PrivatePushFollow') {
setPrivateText(index, data.type)
}
//关注前的返回和右滑
if (data.type == 'Attention') {
}
//发送关注之前的返回
if (data.type == 'addHost') {
}
//发送任务
if (data.type == 'Attention') {
setTimeout(() => {
wslist[index].send(JSON.stringify(data));
resolve();
}, 1000)
} else {
2025-07-01 21:36:08 +08:00
wslist[index].send(JSON.stringify(data));
resolve();
2025-07-30 13:34:49 +08:00
}
// 表示当前任务“已发出”,但不是“已完成”
// 实际完成由 onmessage 中的 success 决定并继续执行队列
};
queue.enqueue(task);
} catch (e) {
console.error('发送任务错误:', e);
}
2025-07-01 21:36:08 +08:00
});
}
// 生成start秒到end秒的随机延迟
function randomDelay(start, end) {
return Math.floor(Math.random() * end * 1000) + start * 1000;
}
//一个0到n之间的随机整数
function getRandomNumber(n) {
return Math.floor(Math.random() * (n + 1));
}
//延迟30-50秒后检测关注
function randomSeeVideo(udid, index) {
2025-07-30 13:34:49 +08:00
const delay = Math.floor(Math.random() * (50 - 30) + 10) * 1000;
playTimer.value[index] = setTimeout(() => {
2025-07-01 21:36:08 +08:00
console.log('观看结束', index);
wsActions.isHost(udid, index)//检测
}, delay);
}
//延迟1-4分钟后秒后检测关注
2025-08-04 13:07:36 +08:00
async function randomDelayAndExecute() {
const minDelay = 1 * 60 * 1000;
const maxDelay = 4 * 60 * 1000;
const randomDelay = Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
console.log(`将延迟 ${Math.round(randomDelay / 1000)} 秒后执行...`);
return new Promise((resolve) => {
setTimeout(() => {
const randomNum = Math.floor(Math.random() * 100);
if (randomNum < 30) {
console.log("30%概率触发");
resolve(true);
} else {
console.log("70%概率触发");
resolve(false);
}
}, randomDelay);
});
}
2025-07-01 21:36:08 +08:00
//选中样式
function getVideoStyle(index) {
const isSelected = selectedDevice.value === index;
const baseWidth = phone.value.width;
const baseHeight = phone.value.height;
2025-08-07 21:35:27 +08:00
2025-07-01 21:36:08 +08:00
return {
width: isSelected ? baseWidth * 1.4 + 'px' : baseWidth + 'px',
height: isSelected ? baseHeight * 1.4 + 'px' : baseHeight + 'px',
border: isSelected ? '2px solid blue' : '1px solid blue',
position: isSelected ? 'absolute' : 'relative',
top: isSelected ? '0' : 'unset',
left: isSelected ? '0' : 'unset',
zIndex: isSelected ? 1000 : 1,
pointerEvents: isSelected ? 'none' : 'auto',
2025-08-07 21:35:27 +08:00
transform: getTransformStyle(index),
transition: 'all 0.3s ease',
2025-07-01 21:36:08 +08:00
};
}
2025-07-30 13:34:49 +08:00
function stop() {
2025-07-01 21:36:08 +08:00
// actions[index] = [];
cloesMonitor(); //关闭监听
2025-07-30 13:34:49 +08:00
isStop.value = true; //停止所有任务
isMsgPop.value = false;//关闭爬虫sse任务
2025-07-30 13:34:49 +08:00
deviceInformation.value.forEach((item, i) => {
console.log('停止', i);
createTaskQueue(i).clear();//清除队伍中的任务
console.log(createTaskQueue(i).getNum())
//停止当前任务
//重置当前状态
runType.value[i] = ''
//清除观看视频定时器
clearInterval(playTimer.value[i])
playTimer.value[i] = null;//清除定时器
2025-07-01 21:36:08 +08:00
})
2025-07-30 13:34:49 +08:00
console.log('停止', isStop.value);
2025-07-01 21:36:08 +08:00
}
function openTk() {
deviceInformation.value.forEach((device, index) => {
wsActions.open(device.udid, index)
})
}
2025-07-30 13:34:49 +08:00
function resetTk() {
isStop.value = false;
deviceInformation.value.forEach((device, index) => {
resetApp(device.udid, index)
})
}
2025-07-01 21:36:08 +08:00
//监听所有手机是否有消息
2025-07-30 13:34:49 +08:00
function openMonitor(type) {
isStop.value = false;
2025-07-01 21:36:08 +08:00
deviceInformation.value.forEach((device, index) => {
wsActions.getmesNum(device.udid, index)
runType.value[index] = 'listen'
})
isShowMes.value = setInterval(() => {
deviceInformation.value.forEach((device, index) => {
wsActions.getmesNum(device.udid, index)
})
}, 10000)
}
2025-07-15 13:45:36 +08:00
2025-07-01 21:36:08 +08:00
//关闭监听
function cloesMonitor() {
isMonitorOn.value = false;//关闭监听
2025-07-01 21:36:08 +08:00
deviceInformation.value.forEach((device, index) => {
runType.value[index] = ''
})
clearInterval(isShowMes.value)
isShowMes.value = ''
}
//一键养号
function parentNum() {
2025-07-30 13:34:49 +08:00
isStop.value = false;
2025-07-01 21:36:08 +08:00
deviceInformation.value.forEach((device, index) => {
2025-07-15 13:45:36 +08:00
runType.value[index] = 'like'
2025-07-01 21:36:08 +08:00
wsActions.isHost(device.udid, index)
})
}
2025-08-04 13:07:36 +08:00
function brushLive() {
isStop.value = false;
deviceInformation.value.forEach((device, index) => {
2025-08-11 22:00:45 +08:00
runType.value[index] = 'brushLive'
2025-08-04 13:07:36 +08:00
wsActions.toLive(device.udid, index)
})
}
2025-07-01 21:36:08 +08:00
//确认多行文本框内容
2025-08-07 21:35:27 +08:00
function onDialogConfirm(result, type, index, isMon) {
console.log(type, index, isMon);
2025-07-01 21:36:08 +08:00
if (type == '评论') {
2025-07-30 13:34:49 +08:00
if (index == 998) {
2025-07-01 21:36:08 +08:00
textContentArr.value.forEach((item, indexA) => {
textContent.value[indexA] = result[getRandomNumber(result.length - 1)];
})
2025-07-30 13:34:49 +08:00
setContentList(result)
2025-07-01 21:36:08 +08:00
//打开私信弹窗
selectedDevice.value = 999;
dialogTitle.value = '私信';
setTimeout(() => {
showDialog.value = true;
}, 600)
} else {
2025-07-30 13:34:49 +08:00
setContentList(result)
2025-07-01 21:36:08 +08:00
textContent.value[index] = result[getRandomNumber(result.length - 1)];
}
} else if (type == '私信') {
//index ==999 表示全部
if (index == 999) {
2025-08-07 21:35:27 +08:00
isMonitor.value = isMon //是否自动回复
2025-07-01 21:36:08 +08:00
textContentpriArr.value.forEach((item, indexA) => {
textContentpri.value[indexA] = result[getRandomNumber(result.length - 1)];
2025-07-30 13:34:49 +08:00
runType.value[indexA] = 'follow'
2025-07-01 21:36:08 +08:00
})
2025-08-07 21:35:27 +08:00
// isStop.value = true; //停止所有任务
2025-07-30 13:34:49 +08:00
setContentpriList(result)
2025-07-01 21:36:08 +08:00
deviceInformation.value.forEach((device, indexB) => {
2025-07-30 13:34:49 +08:00
if (getHostList().length <= 0) return;
2025-08-07 21:35:27 +08:00
if (isMon) {
console.log(isMon)
console.error('自动回复');
wsActions.getmesNum(device.udid, indexB)
} else {
console.error('开始关注');
LikesToLikesToLikes(device.udid, indexB)
}
2025-07-30 13:34:49 +08:00
// LikesToLikesToLikes(device.udid, indexB)
2025-08-07 21:35:27 +08:00
2025-07-01 21:36:08 +08:00
})
} else {
textContentpriArr.value[index] = result;
textContentpri.value[index] = result[getRandomNumber(result.length - 1)];
}
} else if (type == '主播ID') {
//index ==999 表示全部
if (index == 999) {
//分割数组 几台设备 分为几个数组
2025-07-30 13:34:49 +08:00
let hostListResult = [];
result.forEach((item, indexA) => {
hostListResult.push({ country: '', text: item, state: false })
2025-07-01 21:36:08 +08:00
})
2025-07-30 13:34:49 +08:00
setHostList(hostListResult)
2025-07-01 21:36:08 +08:00
//打开评论弹窗
selectedDevice.value = 998;
dialogTitle.value = '评论';
setTimeout(() => {
showDialog.value = true;
}, 600)
}
}
}
2025-07-15 13:45:36 +08:00
function getTranslation(list) {
list.forEach((item, index) => {
2025-07-30 13:34:49 +08:00
translationToChinese({ msg: item.text }).then(res => {
2025-07-15 13:45:36 +08:00
console.log(res);
chatList.value[index].text = res
})
})
2025-07-01 21:36:08 +08:00
}
2025-07-15 13:45:36 +08:00
2025-07-30 13:34:49 +08:00
function markFirstFalseAsTrue(hostList) {
const index = hostList.findIndex(item => item.state === false);
if (index !== -1) {
hostList[index].state = true;
setHostList(hostList)
return hostList[index]; // 可选:返回被修改的对象
}
return null; // 没有找到 false 的项
}
2025-08-07 21:35:27 +08:00
//计算手机canvas是否需要偏移
function getTransformStyle(index) {
return selectedDevice.value === index && index >= 3
? 'translateY(-30%)'
: 'none';
}
2025-07-30 13:34:49 +08:00
2025-08-07 21:35:27 +08:00
function manualGc() {
window.electronAPI.manualGc()
}
2025-08-11 22:00:45 +08:00
2025-08-14 19:52:34 +08:00
// 等待 video 引用就绪的小工具
async function waitForVideoEl(udid, tries = 20, delay = 16) {
for (let i = 0; i < tries; i++) {
const el = videoElement.value?.[udid];
if (el) return el;
await nextTick(); // 等下一次 DOM 刷新
await new Promise(r => setTimeout(r, delay)); // 再小等一帧
}
return null;
}
2025-07-01 21:36:08 +08:00
</script>
<style scoped lang="less">
2025-07-15 13:45:36 +08:00
@import '../static/css/video.less';
2025-07-01 21:36:08 +08:00
</style>