feat: 优化播放控制器和 WebSocket 逻辑,移除不必要的内部状态,增强场景加载后机器人初始化流程
This commit is contained in:
parent
e3f3ab5c2a
commit
3420c7cc9c
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue';
|
import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue';
|
||||||
import { computed, ref, watch, onUnmounted } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { useTimelineTicks } from '../hooks/useTimelineTicks';
|
import { useTimelineTicks } from '../hooks/useTimelineTicks';
|
||||||
|
|
||||||
@ -21,7 +21,6 @@ const SLIDER_PADDING = 8; // Ant Design Slider has 8px padding on each side
|
|||||||
const HOUR_IN_MS = 3600 * 1000;
|
const HOUR_IN_MS = 3600 * 1000;
|
||||||
|
|
||||||
// --- Internal State ---
|
// --- Internal State ---
|
||||||
const internalCurrentTime = ref(props.currentTime);
|
|
||||||
const selectedHour = ref(Math.floor(props.currentTime / HOUR_IN_MS));
|
const selectedHour = ref(Math.floor(props.currentTime / HOUR_IN_MS));
|
||||||
|
|
||||||
// --- Computed View Range ---
|
// --- Computed View Range ---
|
||||||
@ -36,9 +35,6 @@ watch(
|
|||||||
() => props.currentTime,
|
() => props.currentTime,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
// Only update from prop if not playing
|
// Only update from prop if not playing
|
||||||
if (!props.isPlaying) {
|
|
||||||
internalCurrentTime.value = newValue;
|
|
||||||
}
|
|
||||||
const newHour = Math.floor(newValue / HOUR_IN_MS);
|
const newHour = Math.floor(newValue / HOUR_IN_MS);
|
||||||
if (newHour !== selectedHour.value) {
|
if (newHour !== selectedHour.value) {
|
||||||
selectedHour.value = newHour;
|
selectedHour.value = newHour;
|
||||||
@ -47,54 +43,6 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- Animation Frame Timer for Playback ---
|
|
||||||
let animationFrameId: number | null = null;
|
|
||||||
let lastTimestamp: number | null = null;
|
|
||||||
|
|
||||||
const animationLoop = (timestamp: number) => {
|
|
||||||
if (!lastTimestamp) {
|
|
||||||
lastTimestamp = timestamp;
|
|
||||||
}
|
|
||||||
const deltaTime = timestamp - lastTimestamp;
|
|
||||||
lastTimestamp = timestamp;
|
|
||||||
|
|
||||||
// Ensure we don't jump too far ahead if the tab was inactive
|
|
||||||
const clampedDelta = Math.min(deltaTime, 1000); // Max 1-second jump
|
|
||||||
|
|
||||||
const newTime = internalCurrentTime.value + clampedDelta;
|
|
||||||
|
|
||||||
if (newTime < props.totalDuration) {
|
|
||||||
internalCurrentTime.value = newTime;
|
|
||||||
// No need to emit timeupdate as per user decision
|
|
||||||
// emit('timeupdate', newTime);
|
|
||||||
animationFrameId = requestAnimationFrame(animationLoop);
|
|
||||||
} else {
|
|
||||||
internalCurrentTime.value = props.totalDuration;
|
|
||||||
emit('pause'); // Auto-pause at the end
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.isPlaying,
|
|
||||||
(isPlaying) => {
|
|
||||||
if (isPlaying) {
|
|
||||||
lastTimestamp = null; // Reset timestamp on play
|
|
||||||
animationFrameId = requestAnimationFrame(animationLoop);
|
|
||||||
} else {
|
|
||||||
if (animationFrameId) {
|
|
||||||
cancelAnimationFrame(animationFrameId);
|
|
||||||
animationFrameId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (animationFrameId) {
|
|
||||||
cancelAnimationFrame(animationFrameId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- Computed properties for display and binding ---
|
// --- Computed properties for display and binding ---
|
||||||
const formatTime = (ms: number): string => {
|
const formatTime = (ms: number): string => {
|
||||||
if (isNaN(ms) || ms < 0) return '00:00:00';
|
if (isNaN(ms) || ms < 0) return '00:00:00';
|
||||||
@ -105,20 +53,19 @@ const formatTime = (ms: number): string => {
|
|||||||
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentTimeFormatted = computed(() => formatTime(internalCurrentTime.value));
|
const currentTimeFormatted = computed(() => formatTime(props.currentTime));
|
||||||
const totalDurationFormatted = computed(() => formatTime(props.totalDuration));
|
const totalDurationFormatted = computed(() => formatTime(props.totalDuration));
|
||||||
|
|
||||||
const sliderValue = computed({
|
const sliderValue = computed({
|
||||||
get: () => internalCurrentTime.value,
|
get: () => props.currentTime,
|
||||||
set: (val: number) => {
|
set: (val: number) => {
|
||||||
internalCurrentTime.value = val;
|
|
||||||
emit('seek', val);
|
emit('seek', val);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const playheadPosition = computed(() => {
|
const playheadPosition = computed(() => {
|
||||||
if (!props.totalDuration) return '0%';
|
if (!props.totalDuration) return '0%';
|
||||||
const relativeTime = internalCurrentTime.value - viewStartTime.value;
|
const relativeTime = props.currentTime - viewStartTime.value;
|
||||||
const viewDuration = viewEndTime.value - viewStartTime.value;
|
const viewDuration = viewEndTime.value - viewStartTime.value;
|
||||||
return `${(relativeTime / viewDuration) * 100}%`;
|
return `${(relativeTime / viewDuration) * 100}%`;
|
||||||
});
|
});
|
||||||
@ -149,7 +96,6 @@ const handleTimelineClick = (event: MouseEvent) => {
|
|||||||
const newTimeInView = percentage * HOUR_IN_MS;
|
const newTimeInView = percentage * HOUR_IN_MS;
|
||||||
const newAbsoluteTime = viewStartTime.value + newTimeInView;
|
const newAbsoluteTime = viewStartTime.value + newTimeInView;
|
||||||
|
|
||||||
internalCurrentTime.value = newAbsoluteTime;
|
|
||||||
emit('seek', newAbsoluteTime);
|
emit('seek', newAbsoluteTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -62,11 +62,10 @@ export function usePlaybackWebSocket(editorService: ShallowRef<EditorService | u
|
|||||||
});
|
});
|
||||||
client.value = wsInstance;
|
client.value = wsInstance;
|
||||||
|
|
||||||
wsInstance.onopen = () => {
|
// [关键修复] ws.create() 返回时连接已建立,直接执行连接成功逻辑
|
||||||
isConnected.value = true;
|
isConnected.value = true;
|
||||||
message.success('回放连接已建立');
|
message.success('回放连接已建立');
|
||||||
startRenderLoop();
|
startRenderLoop();
|
||||||
};
|
|
||||||
|
|
||||||
wsInstance.onmessage = (event) => {
|
wsInstance.onmessage = (event) => {
|
||||||
const msg = JSON.parse(event.data) as PlaybackMessage;
|
const msg = JSON.parse(event.data) as PlaybackMessage;
|
||||||
@ -79,6 +78,7 @@ export function usePlaybackWebSocket(editorService: ShallowRef<EditorService | u
|
|||||||
sceneJson.value = msg.data;
|
sceneJson.value = msg.data;
|
||||||
latestRobotData.clear();
|
latestRobotData.clear();
|
||||||
} else if (msg.type === 'AMR') {
|
} else if (msg.type === 'AMR') {
|
||||||
|
console.log('[Playback] Received AMR data:', msg.data);
|
||||||
(msg.data as RobotRealtimeInfo[]).forEach(robot => {
|
(msg.data as RobotRealtimeInfo[]).forEach(robot => {
|
||||||
latestRobotData.set(robot.id, robot);
|
latestRobotData.set(robot.id, robot);
|
||||||
});
|
});
|
||||||
@ -138,11 +138,15 @@ export function usePlaybackWebSocket(editorService: ShallowRef<EditorService | u
|
|||||||
};
|
};
|
||||||
|
|
||||||
const batchUpdateRobots = (updates: Array<{ id: string; data: RobotRealtimeInfo }>) => {
|
const batchUpdateRobots = (updates: Array<{ id: string; data: RobotRealtimeInfo }>) => {
|
||||||
|
|
||||||
const editor = editorService.value;
|
const editor = editorService.value;
|
||||||
|
console.log('[Playback] Batch update: Editor service is', editor ? 'available' : 'NOT available', '. Updates to process:', updates);
|
||||||
if (!editor || updates.length === 0) return;
|
if (!editor || updates.length === 0) return;
|
||||||
|
|
||||||
updates.forEach(({ id, data }) => {
|
updates.forEach(({ id, data }) => {
|
||||||
if (editor.checkRobotById(id)) {
|
const robotExists = editor.checkRobotById(id);
|
||||||
|
console.log(`[Playback] Checking robot ID: ${id}. Exists in editor: ${robotExists}`);
|
||||||
|
if (robotExists) {
|
||||||
const { x, y, angle, ...rest } = data;
|
const { x, y, angle, ...rest } = data;
|
||||||
editor.updateRobot(id, rest);
|
editor.updateRobot(id, rest);
|
||||||
editor.setValue({
|
editor.setValue({
|
||||||
@ -157,22 +161,30 @@ export function usePlaybackWebSocket(editorService: ShallowRef<EditorService | u
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderLoop = () => {
|
const renderLoop = () => {
|
||||||
const updates: Array<{ id: string; data: RobotRealtimeInfo }> = [];
|
try {
|
||||||
for (const [id, data] of latestRobotData.entries()) {
|
console.log('[Playback] Render loop executing...');
|
||||||
updates.push({ id, data });
|
const updates: Array<{ id: string; data: RobotRealtimeInfo }> = [];
|
||||||
|
for (const [id, data] of latestRobotData.entries()) {
|
||||||
|
updates.push({ id, data });
|
||||||
|
}
|
||||||
|
latestRobotData.clear();
|
||||||
|
|
||||||
|
if (updates.length > 0) {
|
||||||
|
console.log(`[Playback] Render loop: Preparing to update ${updates.length} robots.`);
|
||||||
|
batchUpdateRobots(updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (isPlaying.value && currentTime.value >= totalDuration.value && totalDuration.value > 0) {
|
||||||
|
// pause();
|
||||||
|
// currentTime.value = totalDuration.value;
|
||||||
|
// }
|
||||||
|
|
||||||
|
editorService.value?.render();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Playback] Error in renderLoop:', error);
|
||||||
|
stopRenderLoop(); // Stop the loop on error to prevent flooding the console
|
||||||
|
return; // Exit the function
|
||||||
}
|
}
|
||||||
latestRobotData.clear();
|
|
||||||
|
|
||||||
if (updates.length > 0) {
|
|
||||||
batchUpdateRobots(updates);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (isPlaying.value && currentTime.value >= totalDuration.value && totalDuration.value > 0) {
|
|
||||||
// pause();
|
|
||||||
// currentTime.value = totalDuration.value;
|
|
||||||
// }
|
|
||||||
|
|
||||||
editorService.value?.render();
|
|
||||||
animationFrameId = requestAnimationFrame(renderLoop);
|
animationFrameId = requestAnimationFrame(renderLoop);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -97,9 +97,11 @@ const handleDateChange = (date: Dayjs) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(playback.sceneJson, (newJson) => {
|
watch(playback.sceneJson, async (newJson) => {
|
||||||
if (newJson) {
|
if (newJson) {
|
||||||
editor.value?.load(newJson);
|
await editor.value?.load(newJson);
|
||||||
|
// [关键修复] 场景加载后,立即重新初始化机器人
|
||||||
|
await editor.value?.initRobots();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user