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