-
+
+ 执行主播库
@@ -38,7 +30,7 @@
打印ui节点树
-
判断单人还是双人
+
下滑
关闭当前应用
安装 APK
@@ -61,6 +53,7 @@
+
@@ -70,7 +63,7 @@ import VideoConverter from "h264-converter";
import { useRouter } from 'vue-router';
import {
setphoneXYinfo, getphoneXYinfo, getUser,
- getHostList, setHostList, getContentpriList,
+ getHostList, setHostList, addToHostList, getContentpriList,
setContentpriList, getContentList, setContentList,
setsessionId, getsessionId
} from '@/stores/storage'
@@ -88,11 +81,17 @@ import { prologue, comment } from '@/api/account';
import { createTaskQueue } from '@/composables/useTaskQueue' //创建任务
import { useCanvasPointer } from '@/composables/useCanvasPointer' //canvas 初始化 点击转换
import { attachTrimmerForIndex } from '@/composables/useVideoStream' //修剪器
+import HostListManagerDialog from '@/components/HostListManagerDialog.vue'
+import { useMonitor } from '@/composables/useMonitor'
+import { useTeardown } from '@/composables/useTeardown' //销毁
+import LeftToolbar from '@/components/LeftToolbar.vue' //左侧工具栏
+import { useStreams } from '@/composables/useStreams'
+
const router = useRouter();
let wsActions = null;
let userdata = getUser();
// 引入刷新方法
-// const reload = inject("reload")
+const reloadPage = inject("reload")
let phone = ref({ width: 207, height: 470 });
const openStr = base64ToBinary("ZQBwAAAAAAA8CgLQAtAAAAAAAAAAAAD/AAAAAAAAAAAAAAAA"); //开启视频流的启动命令
@@ -155,8 +154,7 @@ const mouseData = {
let openShowChat = ref(true);
let istranslate = ref(false); //是否是翻译本页
let phoneXYinfo = ref(getphoneXYinfo() == null ? [{}, {}, {}, {}, {}, {}, {}, {}] : getphoneXYinfo());
-// 当前悬浮的按钮索引
-const hoverIndex = ref(null)
+
const isMonitorOn = ref(false) // false 表示关闭,true 表示开启
// 这四个互斥模式的 key,和你的 runType 对应
const EXCLUSIVE_KEYS = ['brushLive', 'like', 'follow', 'listen'];
@@ -168,7 +166,10 @@ const KEY_LABEL = {
follow: '一键关注并打招呼',
listen: '监测消息',
};
-
+const showHostDlg = ref(false)
+function onHostSaved(list) {
+ console.log('保存后的 HostList:', list)
+}
// 当前激活的互斥 key(runType 里只要是这四个之一就视为锁定)
const activeKey = computed(() => EXCLUSIVE_KEYS.includes(runType.value) ? runType.value : '');
@@ -306,7 +307,10 @@ const buttons = [
},
{
label: '登出',
- onClick: () => router.push('/'),
+ onClick: () => {
+ td.disposeAll('logout')
+ router.push('/')
+ },
show: () => true,
img: {
normal: new URL('@/assets/video/leftBtn9.png', import.meta.url).href,
@@ -315,6 +319,27 @@ const buttons = [
}
]
+
+// 建立 monitor
+const {
+ isPausedByIndex,
+ pauseMonitorByIndex,
+ resumeMonitorByIndex,
+ openMonitor,
+ closeMonitor,
+ stopAll,
+ resumeAndKick,
+} = useMonitor({
+ deviceInformation,
+ runType,
+ isStop,
+ isMonitorOn,
+ isShowMes,
+ wsActionsRef: () => wsActions, // wsActions 是你 onopen 里创建的
+});
+
+
+
// 放在变量都已声明之后(要能拿到 phone、toBuffer、wslist)
const { canvasRef, frameMeta, initCanvas, getCanvasCoordinate, sendPointer } =
useCanvasPointer({
@@ -324,33 +349,36 @@ const { canvasRef, frameMeta, initCanvas, getCanvasCoordinate, sendPointer } =
});
-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));
- }
- }
-}
+// —— 放在变量区(deviceInformation 已经是 ref([]))——
+const pausedDevices = new Set(); // 用 UDID 做键
+
+
+
const wsCache = new Map();
+const td = useTeardown({
+ deviceInformation,
+ wslist,
+ instanceList,
+ videoElement,
+ canvasRef,
+ playTimer,
+ isShowMes,
+ wsCache,
+ createTaskQueue,
+ stopAll, // useMonitor 里的
+ removeDocListeners: () => {
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
+ }
+})
+
+const { feedState, pushFrame, resetFeedState, waitForVideoEl, refreshStream } = useStreams({
+ instanceList,
+ videoElement,
+ wslist,
+ openStr,
+ VideoConverter, // 传入页面已引入的构造器,避免在 composable 重复引入
+})
//````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
// 初始化 手机显示WebSocket 和视频流
const initVideoStream = async (udid, index) => {
@@ -428,15 +456,18 @@ const initVideoStream = async (udid, index) => {
//如果检测到有新消息,会收到两条ws回复,一条message==1 一条message==成功
} else if (resData.message == 1) {
console.log('有消息')
+ pauseMonitorByIndex(index); // 新增:暂停该设备的轮询
} else if (resData.message == '点击成功') {
+ console.log('双击', resData.x, resData.y, index)
console.log('双击', 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)
+ clickxy(resData.x * getphoneXYinfo()[index].width, resData.y * getphoneXYinfo()[index].height, index)
setTimeout(() => {
- clickxy(resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index) //index为9的时候长按
+ clickxy(resData.x * getphoneXYinfo()[index].width, resData.y * getphoneXYinfo()[index].height, index) //index为9的时候长按
+ wsActions.clickSysMesage(deviceInformation.value[index].udid, index) //点击消息进入对话框
}, 100)
- wsActions.clickSysMesage(deviceInformation.value[index].udid, index) //点击消息进入对话框
- }, 1500)
+
+ }, 2000)
}
} else if (resData.type == 'clickMesage') {
//点击进入新消息页面以后,获取页面信息
@@ -449,6 +480,15 @@ const initVideoStream = async (udid, index) => {
if (runType.value == 'follow') {
LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}
+ // 仅监听模式下恢复
+ if (runType.value === 'listen' || isMonitorOn.value) {
+ resumeMonitorByIndex(index);
+ // 轻微延迟后立刻补一次检测
+ setTimeout(() => {
+ const udid = deviceInformation.value[index]?.udid;
+ if (udid) wsActions.getmesNum(udid, index);
+ }, 1500);
+ }
}, 1000)
}, 1000)
@@ -517,6 +557,9 @@ const initVideoStream = async (udid, index) => {
// iponeCoefficient.value[index].height = 720 / scaledH;
iponeCoefficient.value[index].width = scaledW / resData.width
iponeCoefficient.value[index].height = scaledH / resData.height
+ console.log(index)
+ phoneXYinfo.value[index].width = scaledW / resData.width
+ phoneXYinfo.value[index].height = scaledH / resData.height
console.log(
`[getSize] raw=${RAW_W}x${RAW_H} -> scaled=${scaledW}x${scaledH} (align↓${ALIGN}) ${iponeCoefficient.value[index].width} ${iponeCoefficient.value[index].height}`
);
@@ -597,9 +640,13 @@ const initVideoStream = async (udid, index) => {
}, 1000);
} else if (resData.type == 'PrivatePushFollow') {
//如果有新消息,回复完私信以后,返回三次,然后继续下一个任务
- wsActions.getmesNum(deviceInformation.value[index].udid, index)
+ // wsActions.getmesNum(deviceInformation.value[index].udid, index)
// LikesToLikesToLikes(deviceInformation.value[index].udid, index)
+ if (runType.value === 'listen' || isMonitorOn.value) {
+ resumeAndKick(index); // ← 一步到位:恢复并在 1.5s 后补一次 getmesNum
+ }
+
}
}, 1000);
@@ -729,12 +776,17 @@ const initVideoStream = async (udid, index) => {
LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}, 1000)
}, 1000)
- } else if (resData.type == 'Privatetex' || resData.type == 'hostVideo' || resData.type == 'search' || resData.type == 'Attention' || resData.type == 'Comment') {
+ } else if (resData.type == 'PrivatePush' || resData.type == 'Privatetex' || resData.type == 'hostVideo' || resData.type == 'search' || resData.type == 'Attention' || resData.type == 'Comment') {
if (runType.value == 'follow') {
//关注的时候出现无法私信和没有视频的情况 错误重置
resetApp(udid, index)
setTimeout(() => {
- wsActions.getmesNum(deviceInformation.value[index].udid, index)
+ if (isMonitor.value) {
+ //正常没有消息,发送完私信以后,返回六次,然后继续下一个任务
+ wsActions.getmesNum(deviceInformation.value[index].udid, index)
+ } else {
+ LikesToLikesToLikes(deviceInformation.value[index].udid, index)
+ }
// LikesToLikesToLikes(deviceInformation.value[index].udid, index)
}, 1000)
}
@@ -903,12 +955,14 @@ onMounted(() => {
text: '初始化中...',
background: 'rgba(0, 0, 0, 0.7)',
})
+ // reloadPage()
setTimeout(() => {
loading.close()
+
}, 2000)
//sse接收爬虫发送的消息
- connectSSE(`https://datasave.api.yolozs.com/api/sse/connect/${userdata.tenantId}/${userdata.id}`, (data) => {
+ const es = 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)
@@ -933,7 +987,7 @@ onMounted(() => {
type: 'success',
message: '任务开启成功',
})
- setHostList(stroageHost.value)
+ addToHostList(stroageHost.value)
//重启tk
resetTk()
//获取评论
@@ -968,16 +1022,17 @@ onMounted(() => {
})
}
} else {
- // stroageHost.value = getHostList()
+ stroageHost.value = getHostList()
stroageHost.value.push(({ country: data.country, text: data.hostsId, state: false }))
if (runType.value == 'follow') {
- setHostList(stroageHost.value)
+ addToHostList([{ country: data.country, text: data.hostsId, state: false }])
}
}
})
+ td.setSSE(es)
});
//更新状态
// update(
@@ -1003,6 +1058,7 @@ onUnmounted(() => {
const ObtainDeviceInformation = () => {
// 2. 连接 WebSocket
const ws = new WebSocket("ws://127.0.0.1:8000/?action=multiplex");
+ td.setMultiplexWS(ws)
ws.binaryType = "arraybuffer";
ws.onopen = () => {
ws.send(eitwo);
@@ -1016,14 +1072,15 @@ const ObtainDeviceInformation = () => {
deviceInformation.value = [];
const filteredList = data.data.list.filter(item => item.state === 'device');
//检测到设备列表时,渲染所有设备
- for (const item of filteredList) {
+ for (let i = 0; i < filteredList.length; i++) {
+ const item = filteredList[i];
deviceInformation.value.push(item);
await nextTick(); // 等 v-for 渲染出