修复若干bug和ui
This commit is contained in:
@@ -3,9 +3,8 @@
|
||||
<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" :style="{
|
||||
backgroundColor: btn.label == '关闭监测消息' ? 'red' : '',
|
||||
}" @click="btn.onClick" @mouseenter="hoverIndex = index" @mouseleave="hoverIndex = null">
|
||||
<div v-if="btn.show?.()" class="left-button" :style="btn.style ? btn.style() : {}" @click="btn.onClick"
|
||||
@mouseenter="hoverIndex = index" @mouseleave="hoverIndex = null">
|
||||
<img :src="hoverIndex === index ? btn.img.hover : btn.img.normal" alt="">
|
||||
{{ btn.label }}
|
||||
</div>
|
||||
@@ -60,7 +59,7 @@
|
||||
<ChatDialog :visible="openShowChat" :messages="chatList" />
|
||||
</div>
|
||||
<MultiLineInputDialog v-model:visible="showDialog" :initialText='""' :title="dialogTitle" :index="selectedDevice"
|
||||
@confirm="onDialogConfirm" />
|
||||
@confirm="onDialogConfirm" @cancel="stop" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -124,7 +123,7 @@ let isStop = ref(false);
|
||||
//sse弹窗是否存在
|
||||
let isMsgPop = ref(false);
|
||||
//播放器列表
|
||||
let instanceList = ref([{}, {}, {}, {}, {}, {}, {}, {}]);
|
||||
let instanceList = [{}, {}, {}, {}, {}, {}, {}, {}];
|
||||
//是否是在关注主播
|
||||
let runType = ref(['', '', '', '', '', '', '', '']);
|
||||
//屏幕尺寸系数
|
||||
@@ -156,6 +155,7 @@ let istranslate = ref(false); //是否是翻译本页
|
||||
let phoneXYinfo = ref(getphoneXYinfo() == null ? [{}, {}, {}, {}, {}, {}, {}, {}] : getphoneXYinfo());
|
||||
// 当前悬浮的按钮索引
|
||||
const hoverIndex = ref(null)
|
||||
const isMonitorOn = ref(false) // false 表示关闭,true 表示开启
|
||||
|
||||
// 你可以用这种方式声明按钮们
|
||||
const buttons = [
|
||||
@@ -188,25 +188,40 @@ const buttons = [
|
||||
},
|
||||
{
|
||||
label: '打开直播',
|
||||
onClick: () => brushLive(),
|
||||
onClick: () => {
|
||||
runType.value[0] = 'brushLive'
|
||||
brushLive()
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
style: () => ({
|
||||
backgroundColor: runType.value[0] == 'brushLive' ? 'red' : ''
|
||||
})
|
||||
},
|
||||
{
|
||||
label: '一键养号',
|
||||
onClick: () => parentNum(),
|
||||
onClick: () => {
|
||||
if (runType.value[0] == 'like') return;
|
||||
runType.value[0] = 'like'
|
||||
parentNum()
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
style: () => ({
|
||||
backgroundColor: runType.value[0] == 'like' ? 'red' : ''
|
||||
})
|
||||
},
|
||||
{
|
||||
label: '一键关注并打招呼',
|
||||
onClick: () => {
|
||||
if (runType.value[0] == 'follow') return;
|
||||
runType.value[0] = 'follow'
|
||||
showDialog.value = true
|
||||
dialogTitle.value = '主播ID'
|
||||
selectedDevice.value = 999
|
||||
@@ -215,26 +230,29 @@ const buttons = [
|
||||
img: {
|
||||
normal: new URL('@/assets/video/leftBtn6.png', import.meta.url).href,
|
||||
hover: new URL('@/assets/video/leftBtn6-6.png', import.meta.url).href
|
||||
}
|
||||
},
|
||||
style: () => ({
|
||||
backgroundColor: runType.value[0] == 'follow' ? 'red' : ''
|
||||
})
|
||||
},
|
||||
{
|
||||
label: '开启监测消息',
|
||||
onClick: () => openMonitor(),
|
||||
show: () => !isShowMes.value, // 只有在未开启时显示
|
||||
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: '关闭监测消息',
|
||||
onClick: () => cloesMonitor(),
|
||||
show: () => isShowMes.value, // 只有在已开启时显示
|
||||
label: '监测消息',
|
||||
onClick: () => {
|
||||
isMonitorOn.value = !isMonitorOn.value
|
||||
if (isMonitorOn.value) {
|
||||
openMonitor()
|
||||
} else {
|
||||
cloesMonitor()
|
||||
}
|
||||
},
|
||||
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
|
||||
},
|
||||
style: { backgroundColor: 'red' }
|
||||
style: () => ({
|
||||
backgroundColor: isMonitorOn.value ? 'red' : ''
|
||||
})
|
||||
},
|
||||
{
|
||||
label: '全部停止',
|
||||
@@ -256,6 +274,31 @@ const buttons = [
|
||||
}
|
||||
]
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
const wsCache = new Map();
|
||||
|
||||
//``````````````````````````````````````````````````````````````````````````````````
|
||||
@@ -264,30 +307,29 @@ const initVideoStream = (udid, index) => {
|
||||
//``````````````````````````````````````````````````````````````````````````````````
|
||||
// 1. 检查缓存中是否已有实例
|
||||
if (wsCache.has(udid)) {
|
||||
const cachedWs = wsCache.get(udid);
|
||||
if (cachedWs.readyState === WebSocket.OPEN) {
|
||||
return cachedWs;
|
||||
const cached = wsCache.get(udid);
|
||||
if (cached?.ws?.readyState === WebSocket.OPEN) {
|
||||
return cached.ws;
|
||||
}
|
||||
// 如果连接已关闭,清除缓存并重新创建
|
||||
wsCache.delete(udid);
|
||||
}
|
||||
// 2. 创建专用实例容器
|
||||
instanceList.value[index] = {
|
||||
wsVideo: null,
|
||||
instanceList[index] = {
|
||||
// wsVideo: null,
|
||||
converter: null,
|
||||
timer: null
|
||||
};
|
||||
//``````````````````````````````````````````````````````````````````````````````````
|
||||
if (!videoElement.value) return;
|
||||
// 1. 创建 h264-converter 实例
|
||||
instanceList.value[index].converter = new VideoConverter(videoElement.value[udid], 60, 1);
|
||||
// instanceList.value[index].converter.play();
|
||||
instanceList[index].converter = new VideoConverter(videoElement.value[udid], 60, 1);
|
||||
// 2. 连接 WebSocket
|
||||
wslist[index] = new WebSocket(
|
||||
`ws://127.0.0.1:8000/?action=proxy-adb&remote=tcp%3A8886&udid=${udid}`
|
||||
);
|
||||
wslist[index].binaryType = "arraybuffer";
|
||||
|
||||
attachTrimmerForIndex(index, 10, 2000); // 挂上修剪器
|
||||
wslist[index].onopen = () => {
|
||||
console.log("手机显示ws已开启");
|
||||
wsActions = createWsActions(wslist);
|
||||
@@ -295,12 +337,12 @@ const initVideoStream = (udid, index) => {
|
||||
setTimeout(() => {
|
||||
wslist[index].send(openStr);
|
||||
}, 300);
|
||||
wsCache.set(udid, instanceList.value[index]);
|
||||
wsCache.set(udid, { ws: wslist[index], index });
|
||||
};
|
||||
const magicSize = stringToUtf8ByteArray('scrcpy_message');
|
||||
// 3. 处理接收到的二进制数据
|
||||
wslist[index].onmessage = (event) => {
|
||||
const data = new Uint8Array(event.data);
|
||||
|
||||
//判断返回的如果是字符串为自定义返回
|
||||
if (typeof event.data == 'string') {
|
||||
if (isStop.value) {
|
||||
@@ -359,7 +401,13 @@ const initVideoStream = (udid, index) => {
|
||||
console.log('最新消息', mesBox)
|
||||
console.log("翻译", istranslate.value)
|
||||
if (istranslate.value == false) {
|
||||
if (mesBox.position == 'right') return
|
||||
if (mesBox.position == 'right') {
|
||||
Back('', index)
|
||||
setTimeout(() => {
|
||||
Back('', index)
|
||||
}, 1000)
|
||||
return
|
||||
}
|
||||
openShowChat.value = true
|
||||
console.log("执行ai")
|
||||
|
||||
@@ -619,21 +667,28 @@ const initVideoStream = (udid, index) => {
|
||||
}
|
||||
// createTaskQueue(index).next(); // 继续队列中下一个任务
|
||||
}
|
||||
}
|
||||
//返回粘贴板内容
|
||||
if (startsWithHeader(magicSize, data)) {
|
||||
if (!isSend.value) {
|
||||
const buffer = trimLongArray(data, magicSize);
|
||||
const paste = bufferToString(buffer);
|
||||
console.log('获取粘贴板内容', paste)
|
||||
}
|
||||
}
|
||||
//视频流处理
|
||||
if (instanceList.value[index].converter) {
|
||||
if (isshow.value) {
|
||||
instanceList.value[index].converter.appendRawData(data);
|
||||
} 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; // 这类消息不走视频通道
|
||||
}
|
||||
|
||||
// 视频流
|
||||
if (instanceList[index].converter && isshow.value) {
|
||||
pushFrame(index, buf); // 用下方新的 pushFrame
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
// 4. 错误处理
|
||||
wslist[index].onerror = (error) => {
|
||||
@@ -642,7 +697,7 @@ const initVideoStream = (udid, index) => {
|
||||
//``````````````````````````````````````````````````````````````````````````````````
|
||||
wslist[index].onclose = (event) => {
|
||||
wsCache.delete(udid)// 自动清理缓存
|
||||
clearInterval(instanceList.value[index].timer); // 清理定时器// 移除缓存
|
||||
clearInterval(instanceList[index].timer); // 清理定时器// 移除缓存
|
||||
};
|
||||
//``````````````````````````````````````````````````````````````````````````````````
|
||||
};
|
||||
@@ -1402,7 +1457,7 @@ function parentNum() {
|
||||
function brushLive() {
|
||||
isStop.value = false;
|
||||
deviceInformation.value.forEach((device, index) => {
|
||||
// runType.value[index] = 'brushLive'
|
||||
runType.value[index] = 'brushLive'
|
||||
wsActions.toLive(device.udid, index)
|
||||
})
|
||||
}
|
||||
@@ -1497,10 +1552,91 @@ function getTransformStyle(index) {
|
||||
? 'translateY(-30%)'
|
||||
: 'none';
|
||||
}
|
||||
function attachTrimmerForIndex(index, backBufferSec = 10, intervalMs = 2000) {
|
||||
const conv = instanceList[index]?.converter;
|
||||
if (!conv) return;
|
||||
|
||||
// 如果还没创建好 MSE/SourceBuffer,则等 sourceopen 再挂
|
||||
const ensureAttach = () => {
|
||||
const ms = conv.mediaSource;
|
||||
if (!ms) return false;
|
||||
if (ms.readyState !== 'open') return false;
|
||||
if (!conv.sourceBuffer) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
// 先清旧的
|
||||
if (conv._trimTimer) {
|
||||
clearInterval(conv._trimTimer);
|
||||
conv._trimTimer = null;
|
||||
}
|
||||
if (conv._mseListenersInstalled !== true && conv.mediaSource) {
|
||||
conv._mseListenersInstalled = true;
|
||||
conv.mediaSource.addEventListener('sourceopen', () => {
|
||||
// MSE 重新 open 时,重新挂修剪器
|
||||
attachTrimmerForIndex(index, backBufferSec, intervalMs);
|
||||
});
|
||||
conv.mediaSource.addEventListener('sourceclose', () => {
|
||||
if (conv._trimTimer) {
|
||||
clearInterval(conv._trimTimer);
|
||||
conv._trimTimer = null;
|
||||
}
|
||||
});
|
||||
conv.mediaSource.addEventListener('error', () => {
|
||||
if (conv._trimTimer) {
|
||||
clearInterval(conv._trimTimer);
|
||||
conv._trimTimer = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 可能还没 ready,轮询等待
|
||||
if (!ensureAttach()) {
|
||||
const waitId = setInterval(() => {
|
||||
if (ensureAttach()) {
|
||||
clearInterval(waitId);
|
||||
attachTrimmerForIndex(index, backBufferSec, intervalMs);
|
||||
}
|
||||
}, 300);
|
||||
return;
|
||||
}
|
||||
|
||||
conv._trimTimer = setInterval(() => {
|
||||
// 每次都重新取,避免拿到被移除的旧 sb 引用
|
||||
const currentConv = instanceList[index]?.converter;
|
||||
const ms = currentConv?.mediaSource;
|
||||
const sb = currentConv?.sourceBuffer;
|
||||
const video = videoElement.value[deviceInformation.value[index]?.udid];
|
||||
|
||||
if (!currentConv || !ms || ms.readyState !== 'open' || !sb || !video) return;
|
||||
if (sb.updating || video.seeking || video.readyState < 2) return;
|
||||
|
||||
const cur = video.currentTime || 0;
|
||||
const trimTo = Math.max(0, cur - backBufferSec);
|
||||
|
||||
try {
|
||||
// buffered 可能是多段,仅删除完全早于 trimTo 的最前一段
|
||||
for (let i = 0; i < sb.buffered.length; i++) {
|
||||
const start = sb.buffered.start(i);
|
||||
const end = sb.buffered.end(i);
|
||||
if (end < trimTo - 0.25) {
|
||||
try { sb.remove(0, end); } catch { }
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 包含 “SourceBuffer has been removed ...” 等异常时,停止本轮,等下次 tick 重取 sb
|
||||
// console.warn('[trimmer]', e);
|
||||
}
|
||||
}, intervalMs);
|
||||
}
|
||||
|
||||
|
||||
function manualGc() {
|
||||
window.electronAPI.manualGc()
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
Reference in New Issue
Block a user