feat: enforce 100% frame-based positioning (no time)
Core Principle: Frame is the ONLY standard Changes: - VideoPlayer: Remove startTime/endTime props - VideoPlayer: Add mandatory startFrame/endFrame check - VideoPlayer: UI shows F123 (51.2s) format - All Views: Only set startFrame/endFrame - All Views: Only pass :start-frame/:end-frame - Remove all startTime/endTime from currentVideo refs Time is only used for display (auxiliary). Frame is the source of truth for all positioning.
This commit is contained in:
@@ -139,7 +139,7 @@
|
||||
<span class="ms-video-seg-info2">{{ currentItemIdx + 1 }} / {{ totalItems }}</span>
|
||||
<button class="ms-fm-btn ms-video-nav-btn" @click="nextItem" :disabled="!canNext">下一個 →</button>
|
||||
</div>
|
||||
<span v-else class="ms-video-seg-info">{{ formatTime(currentTime) }} / {{ formatTime(duration) }}</span>
|
||||
<span v-else class="ms-video-seg-info">F{{ currentFrame }} / F{{ totalFrames }} <span class="ms-video-time-aux">({{ formatTime(currentTime) }} / {{ formatTime(duration) }})</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,10 +159,8 @@ const zIndexValue = ref(getNextZIndex())
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
fileUuid: string
|
||||
startTime?: number
|
||||
endTime?: number
|
||||
startFrame?: number
|
||||
endFrame?: number
|
||||
startFrame: number
|
||||
endFrame: number
|
||||
allTraces?: any[]
|
||||
mergedSegments?: any[]
|
||||
initialTraceIdx?: number
|
||||
@@ -171,6 +169,8 @@ const props = withDefaults(defineProps<{
|
||||
simple?: boolean
|
||||
useOriginal?: boolean
|
||||
}>(), {
|
||||
startFrame: 0,
|
||||
endFrame: 0,
|
||||
allTraces: () => [],
|
||||
mergedSegments: () => [],
|
||||
initialTraceIdx: 0,
|
||||
@@ -888,17 +888,20 @@ onMounted(async () => {
|
||||
emit('trace-change', currentTraceIdx.value)
|
||||
} else {
|
||||
try {
|
||||
const params: any = {
|
||||
if (props.startFrame == null || props.endFrame == null) {
|
||||
videoError.value = 'ERROR: startFrame and endFrame are required'
|
||||
videoLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const data = await apiCall('get_video_stream', {
|
||||
uuid: props.fileUuid,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
startFrame: props.startFrame,
|
||||
endFrame: props.endFrame,
|
||||
original: props.useOriginal,
|
||||
}
|
||||
|
||||
if (props.startFrame != null && props.endFrame != null) {
|
||||
params.startFrame = props.startFrame
|
||||
params.endFrame = props.endFrame
|
||||
}
|
||||
|
||||
const data = await apiCall('get_video_stream', params)
|
||||
})
|
||||
if (typeof data === 'string') {
|
||||
videoSrc.value = data
|
||||
} else {
|
||||
@@ -970,7 +973,8 @@ onUnmounted(() => {
|
||||
.ms-video-tl-pos-tip { display: none; position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: #1a56db; color: #fff; font-size: 10px; padding: 2px 6px; border-radius: 4px; white-space: nowrap; pointer-events: none; z-index: 10; }
|
||||
.ms-video-tl-position:hover .ms-video-tl-pos-tip { display: block; }
|
||||
.ms-video-nav { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; gap: 10px; flex-wrap: wrap; }
|
||||
.ms-video-seg-info { font-size: 12px; color: rgba(255,255,255,0.6); flex-shrink: 0; }
|
||||
.ms-video-seg-info { font-size: 12px; color: rgba(255,255,255,0.9); flex-shrink: 0; font-weight: 500; }
|
||||
.ms-video-time-aux { font-size: 11px; color: rgba(255,255,255,0.5); margin-left: 6px; }
|
||||
.ms-video-seg-info2 { font-size: 12px; color: #9aa0a6; flex: 1; text-align: center; }
|
||||
|
||||
/* Frame seek controls */
|
||||
|
||||
Reference in New Issue
Block a user