feat: 优化播放控制器中的时间同步逻辑,增强时间轴点击交互,确保选择器与视窗一致
This commit is contained in:
parent
ab2cd0d346
commit
7794c91eca
@ -34,10 +34,13 @@ const { ticks } = useTimelineTicks(viewStartTime, viewEndTime);
|
|||||||
watch(
|
watch(
|
||||||
() => props.currentTime,
|
() => props.currentTime,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
// Only update from prop if not playing
|
// 仅在有效范围内才同步小时;避免负数/异常 currentTime 破坏选择器
|
||||||
const newHour = Math.floor(newValue / HOUR_IN_MS);
|
if (Number.isFinite(newValue) && newValue >= 0 && props.totalDuration > 0) {
|
||||||
if (newHour !== selectedHour.value) {
|
const maxHour = Math.max(Math.ceil(props.totalDuration / HOUR_IN_MS) - 1, 0);
|
||||||
selectedHour.value = newHour;
|
const newHour = Math.min(Math.max(Math.floor(newValue / HOUR_IN_MS), 0), maxHour);
|
||||||
|
if (newHour !== selectedHour.value) {
|
||||||
|
selectedHour.value = newHour;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
@ -56,18 +59,32 @@ const formatTime = (ms: number): string => {
|
|||||||
const currentTimeFormatted = computed(() => formatTime(props.currentTime));
|
const currentTimeFormatted = computed(() => formatTime(props.currentTime));
|
||||||
const totalDurationFormatted = computed(() => formatTime(props.totalDuration));
|
const totalDurationFormatted = computed(() => formatTime(props.totalDuration));
|
||||||
|
|
||||||
|
// 本地“乐观显示”的时间:在 seek 后先行展示,待实际时间变更后清空
|
||||||
|
const pendingSeekTime = ref<number | null>(null);
|
||||||
|
|
||||||
|
// 同步清理:当外部 currentTime 改变时,移除乐观显示
|
||||||
|
watch(
|
||||||
|
() => props.currentTime,
|
||||||
|
() => {
|
||||||
|
pendingSeekTime.value = null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const sliderValue = computed({
|
const sliderValue = computed({
|
||||||
get: () => props.currentTime,
|
get: () => pendingSeekTime.value ?? props.currentTime,
|
||||||
set: (val: number) => {
|
set: (val: number) => {
|
||||||
|
pendingSeekTime.value = val;
|
||||||
emit('seek', val);
|
emit('seek', val);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const playheadPosition = computed(() => {
|
const playheadPosition = computed(() => {
|
||||||
if (!props.totalDuration) return '0%';
|
|
||||||
const relativeTime = props.currentTime - viewStartTime.value;
|
|
||||||
const viewDuration = viewEndTime.value - viewStartTime.value;
|
const viewDuration = viewEndTime.value - viewStartTime.value;
|
||||||
return `${(relativeTime / viewDuration) * 100}%`;
|
if (viewDuration <= 0) return '0%';
|
||||||
|
const current = pendingSeekTime.value ?? props.currentTime;
|
||||||
|
const relativeTime = current - viewStartTime.value;
|
||||||
|
const clamped = Math.min(Math.max(relativeTime, 0), viewDuration);
|
||||||
|
return `${(clamped / viewDuration) * 100}%`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hourOptions = computed(() => {
|
const hourOptions = computed(() => {
|
||||||
@ -85,20 +102,6 @@ const hourOptions = computed(() => {
|
|||||||
// --- Event Handlers ---
|
// --- Event Handlers ---
|
||||||
const timelineContainerRef = ref<HTMLElement | null>(null);
|
const timelineContainerRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const handleTimelineClick = (event: MouseEvent) => {
|
|
||||||
if (!timelineContainerRef.value || !props.totalDuration) return;
|
|
||||||
|
|
||||||
const rect = timelineContainerRef.value.getBoundingClientRect();
|
|
||||||
const effectiveWidth = rect.width - SLIDER_PADDING * 2;
|
|
||||||
const clickX = event.clientX - rect.left - SLIDER_PADDING;
|
|
||||||
|
|
||||||
const percentage = Math.max(0, Math.min(1, clickX / effectiveWidth));
|
|
||||||
const newTimeInView = percentage * HOUR_IN_MS;
|
|
||||||
const newAbsoluteTime = viewStartTime.value + newTimeInView;
|
|
||||||
|
|
||||||
emit('seek', newAbsoluteTime);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePlayPause = () => {
|
const handlePlayPause = () => {
|
||||||
if (props.isPlaying) {
|
if (props.isPlaying) {
|
||||||
emit('pause');
|
emit('pause');
|
||||||
@ -106,6 +109,28 @@ const handlePlayPause = () => {
|
|||||||
emit('play');
|
emit('play');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 根据点击时间轴位置计算对应时间并触发 seek
|
||||||
|
const handleTimelineClick = (event: MouseEvent) => {
|
||||||
|
const container = timelineContainerRef.value;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const effectiveWidth = Math.max(rect.width - SLIDER_PADDING * 2, 1);
|
||||||
|
const clampedX = Math.min(Math.max(event.clientX - rect.left - SLIDER_PADDING, 0), effectiveWidth);
|
||||||
|
const ratio = clampedX / effectiveWidth;
|
||||||
|
const viewDuration = viewEndTime.value - viewStartTime.value;
|
||||||
|
const targetTime = Math.floor(viewStartTime.value + ratio * viewDuration);
|
||||||
|
pendingSeekTime.value = targetTime;
|
||||||
|
emit('seek', targetTime);
|
||||||
|
// 点击发生后,立即更新所选小时,保持左侧小时选择与视窗一致
|
||||||
|
const clickedHour = Math.floor(targetTime / HOUR_IN_MS);
|
||||||
|
const maxHour = Math.max(Math.ceil(props.totalDuration / HOUR_IN_MS) - 1, 0);
|
||||||
|
const clampedHour = Math.min(Math.max(clickedHour, 0), maxHour);
|
||||||
|
if (clampedHour !== selectedHour.value) {
|
||||||
|
selectedHour.value = clampedHour;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -190,7 +215,6 @@ const handlePlayPause = () => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 20px; // Space for tick labels
|
padding-top: 20px; // Space for tick labels
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.playhead-marker {
|
.playhead-marker {
|
||||||
|
|||||||
@ -42,7 +42,8 @@ export function useTimelineTicks(viewStartTime: Ref<number>, viewEndTime: Ref<nu
|
|||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
position: `${(relativeTime / viewDuration) * 100}%`,
|
position: `${(relativeTime / viewDuration) * 100}%`,
|
||||||
label: isMajor ? formatLabelTime(time) : null,
|
// 使用相对视窗的时间展示,避免绝对时间导致的大数/负数
|
||||||
|
label: isMajor ? formatLabelTime(relativeTime) : null,
|
||||||
isMajor,
|
isMajor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user