分包+主播库
This commit is contained in:
@@ -1,16 +1,8 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-scrollbar class="left"> <!-- 左边栏 -->
|
||||
<div class="center-line"> <!-- 左边栏按钮 -->
|
||||
<div v-for="(btn, index) in buttons" :key="index" style="width: 100%;">
|
||||
<div v-if="btn.show?.()" class="left-button" :class="[{ active: isActive(btn), disabled: isDisabled(btn) }]"
|
||||
:style="btn.style ? btn.style() : {}" @click="handleBtnClick(btn)" @mouseenter="hoverIndex = index"
|
||||
@mouseleave="hoverIndex = null">
|
||||
<img :src="hoverIndex === index ? btn.img.hover : btn.img.normal" alt="">
|
||||
{{ btn.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<LeftToolbar :buttons="buttons" :active-key="activeKey" :is-locked="isLocked" @click="handleBtnClick" />
|
||||
<el-button style="position: absolute;left: 20px; bottom: 20px;" @click="showHostDlg = true">执行主播库</el-button>
|
||||
</el-scrollbar>
|
||||
<!-- 中间手机区域 -->
|
||||
<div class="content" @click.self="selectedDevice = 999">
|
||||
@@ -38,7 +30,7 @@
|
||||
<!-- <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.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
|
||||
@@ -61,6 +53,7 @@
|
||||
</div>
|
||||
<MultiLineInputDialog v-model:visible="showDialog" :initialText='""' :title="dialogTitle" :index="selectedDevice"
|
||||
@confirm="onDialogConfirm" @cancel="stop" />
|
||||
<HostListManagerDialog v-model:visible="showHostDlg" @save="onHostSaved" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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 渲染出 <video>
|
||||
initCanvas(item.udid); // 如果它也依赖 DOM,同样要在 nextTick 之后
|
||||
initVideoStream(item.udid, deviceInformation.value.length - 1);
|
||||
initVideoStream(item.udid, i); // 直接使用循环变量 i
|
||||
// getSize 建议放到 wslist[index].onopen 里最稳,
|
||||
// 若保留延时也可以:
|
||||
setTimeout(() => wsActions?.getSize(item.udid, deviceInformation.value.length - 1), 2000);
|
||||
setTimeout(() => wsActions?.getSize(item.udid, i), 2000); // 直接使用循环变量 i
|
||||
}
|
||||
} else if (data.type == "device") {
|
||||
if (data.data.device.state === "offline") {
|
||||
@@ -1234,8 +1291,11 @@ async function drag(udid, index, x1, y1, x2, y2, durationMs = 300, steps = 8) {
|
||||
|
||||
// 用 pointer down/up/move 改写后的 clickxy
|
||||
async function clickxy(x, y, index, type) {
|
||||
const udid = deviceInformation.value[index]?.udid;
|
||||
if (!udid) return;
|
||||
const udid = deviceInformation.value[index].udid;
|
||||
if (!udid) {
|
||||
console.error('clickxy: no udid');
|
||||
return;
|
||||
};
|
||||
|
||||
try {
|
||||
if (type === 3) {
|
||||
@@ -1283,16 +1343,9 @@ async function clickxy(x, y, index, type) {
|
||||
}
|
||||
}
|
||||
|
||||
// 清空喂帧状态(避免旧帧冲突)
|
||||
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 { onlySelected = false, hard = true } = opts;
|
||||
const targets = (onlySelected && selectedDevice.value !== 999)
|
||||
? [selectedDevice.value]
|
||||
: deviceInformation.value.map((_, i) => i);
|
||||
@@ -1301,43 +1354,6 @@ const reload = (opts = {}) => {
|
||||
ElMessage.success(`已刷新${onlySelected ? '当前设备' : '全部设备'}`);
|
||||
};
|
||||
|
||||
/** 重建某台设备的视频解码器,不动 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) { }
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
//发送任务前的处理
|
||||
function sendWsTask(index, data) {
|
||||
@@ -1450,7 +1466,7 @@ function getVideoStyle(index) {
|
||||
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',
|
||||
// border: isSelected ? '2px solid blue' : '1px solid blue',
|
||||
position: isSelected ? 'absolute' : 'relative',
|
||||
top: isSelected ? '0' : 'unset',
|
||||
left: isSelected ? '0' : 'unset',
|
||||
@@ -1463,7 +1479,7 @@ function getVideoStyle(index) {
|
||||
|
||||
function stop() {
|
||||
// actions[index] = [];
|
||||
cloesMonitor(); //关闭监听
|
||||
stopAll(); // ← 替代 cloesMonitor + pausedDevices.clear
|
||||
isStop.value = true; //停止所有任务
|
||||
isMsgPop.value = false;//关闭爬虫sse任务
|
||||
|
||||
@@ -1495,29 +1511,15 @@ function resetTk() {
|
||||
resetApp(device.udid, index)
|
||||
})
|
||||
}
|
||||
//监听所有手机是否有消息
|
||||
function openMonitor(type) {
|
||||
isStop.value = false;
|
||||
|
||||
deviceInformation.value.forEach((device, index) => {
|
||||
wsActions.getmesNum(device.udid, index)
|
||||
runType.value = 'listen'
|
||||
})
|
||||
isShowMes.value = setInterval(() => {
|
||||
deviceInformation.value.forEach((device, index) => {
|
||||
wsActions.getmesNum(device.udid, index)
|
||||
})
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
//关闭监听
|
||||
function cloesMonitor() {
|
||||
isMonitorOn.value = false;//关闭监听
|
||||
deviceInformation.value.forEach((device, index) => {
|
||||
runType.value = ''
|
||||
})
|
||||
clearInterval(isShowMes.value)
|
||||
isShowMes.value = ''
|
||||
isMonitorOn.value = false;
|
||||
deviceInformation.value.forEach(() => { runType.value = '' });
|
||||
clearInterval(isShowMes.value);
|
||||
isShowMes.value = '';
|
||||
pausedDevices.clear(); // 新增
|
||||
}
|
||||
|
||||
//一键养号
|
||||
@@ -1591,7 +1593,7 @@ function onDialogConfirm(result, type, index, isMon) {
|
||||
result.forEach((item, indexA) => {
|
||||
hostListResult.push({ country: '', text: item, state: false })
|
||||
})
|
||||
setHostList(hostListResult)
|
||||
addToHostList(hostListResult)
|
||||
//打开评论弹窗
|
||||
selectedDevice.value = 998;
|
||||
dialogTitle.value = '评论';
|
||||
@@ -1631,16 +1633,7 @@ function manualGc() {
|
||||
window.electronAPI.manualGc()
|
||||
}
|
||||
|
||||
// 等待 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;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
Reference in New Issue
Block a user