feat: 在播放控制器中添加本地时间管理和定时器逻辑,优化时间同步与用户交互体验

This commit is contained in:
xudan 2025-10-10 10:22:49 +08:00
parent 6a5a72828f
commit 6fb243370d

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue';
import { computed, ref, watch } from 'vue';
import { computed, ref, watch, onUnmounted } from 'vue';
import { useTimelineTicks } from '../hooks/useTimelineTicks';
@ -21,6 +21,9 @@ const SLIDER_PADDING = 8; // Ant Design Slider has 8px padding on each side
const HOUR_IN_MS = 3600 * 1000;
// --- Internal State ---
const localCurrentTime = ref(props.currentTime);
let timer: number | undefined;
const selectedHour = ref(Math.floor(props.currentTime / HOUR_IN_MS));
const isUserInteracting = ref(false); // props
@ -32,14 +35,48 @@ const viewEndTime = computed(() => viewStartTime.value + HOUR_IN_MS);
const { ticks } = useTimelineTicks(viewStartTime, viewEndTime);
// --- Sync internal time with external prop ---
watch(
() => props.isPlaying,
(playing) => {
if (playing) {
clearInterval(timer); // Clear any existing timer
timer = window.setInterval(() => {
localCurrentTime.value += 1000;
}, 1000);
} else {
clearInterval(timer);
}
},
{ immediate: true },
);
onUnmounted(() => {
clearInterval(timer);
});
watch(
() => props.currentTime,
(newValue) => {
// Only sync from prop if the difference is significant (e.g., > 1.5s)
// This prevents conflicts between the internal timer and external updates during playback.
const diff = Math.abs(newValue - localCurrentTime.value);
if (Number.isFinite(newValue) && newValue >= 0 && diff > 1500) {
localCurrentTime.value = newValue;
}
},
);
// --- Watch local time to sync UI components like hour selector ---
watch(
localCurrentTime,
(newValue) => {
//
if (isUserInteracting.value) {
return;
}
// / currentTime
if (Number.isFinite(newValue) && newValue >= 0 && props.totalDuration > 0) {
const maxHour = Math.max(Math.ceil(props.totalDuration / HOUR_IN_MS) - 1, 0);
@ -62,13 +99,13 @@ const formatTime = (ms: number): string => {
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
};
const currentTimeFormatted = computed(() => formatTime(props.currentTime));
const currentTimeFormatted = computed(() => formatTime(localCurrentTime.value));
const totalDurationFormatted = computed(() => formatTime(props.totalDuration));
const playheadPosition = computed(() => {
const viewDuration = viewEndTime.value - viewStartTime.value;
if (viewDuration <= 0) return '0%';
const current = props.currentTime;
const current = localCurrentTime.value;
const relativeTime = current - viewStartTime.value;
const clamped = Math.min(Math.max(relativeTime, 0), viewDuration);
return `${(clamped / viewDuration) * 100}%`;
@ -105,6 +142,7 @@ const handleHourChange = (newHour: number) => {
const newTime = newHour * HOUR_IN_MS;
//
const clampedTime = Math.min(newTime, props.totalDuration);
localCurrentTime.value = clampedTime; // Immediately update local time
emit('seek', clampedTime);
};
@ -128,6 +166,7 @@ const handleTimelineClick = (event: MouseEvent) => {
const ratio = clampedX / effectiveWidth;
const viewDuration = viewEndTime.value - viewStartTime.value;
const targetTime = Math.floor(viewStartTime.value + ratio * viewDuration);
localCurrentTime.value = targetTime; // Immediately update local time
emit('seek', targetTime);
//
const clickedHour = Math.floor(targetTime / HOUR_IN_MS);