tauri-app: fix video control bar

This commit is contained in:
2026-05-17 14:52:04 +02:00
parent 5f967f0393
commit 36e1031bb1

View File

@@ -524,193 +524,195 @@ onUnmounted(() => {
<div class="review-content">
<!-- Video Section -->
<div class="video-section with-sidebar">
<!-- Video Player -->
<div class="video-container">
<div v-if="videoError" class="video-error">
<div class="error-icon"></div>
<p>{{ videoError }}</p>
</div>
<video
v-else-if="videoUrl"
ref="videoRef"
class="video-player"
@loadedmetadata="onVideoLoaded"
@timeupdate="onVideoTimeUpdate"
@play="onVideoPlay"
@pause="onVideoPause"
@ended="onVideoEnded"
@error="onVideoError"
>
<source :src="videoUrl" type="video/mp4" />
Your browser does not support the video tag.
</video>
<div v-else class="video-loading">
<div class="spinner"></div>
<p>Loading video...</p>
</div>
<!-- Video Overlay Controls -->
<div class="video-overlay" v-if="videoUrl && !videoError">
<!-- Play/Pause Overlay -->
<div class="play-overlay" @click="togglePlay">
<div class="play-btn-large" v-if="!isPlaying">
<span></span>
</div>
</div>
</div>
</div>
<!-- Video Controls -->
<div class="video-controls" v-if="videoUrl && !videoError">
<!-- Timeline with events -->
<div class="timeline-container">
<div class="timeline-bar" @click="onTimelineClick">
<!-- Progress bar -->
<div class="timeline-progress" :style="{ width: `${(currentTime / duration) * 100}%` }"></div>
<!-- Clip selection region -->
<div
v-if="clipStart !== null && clipEnd !== null"
class="clip-region"
:style="{
left: `${(clipStart / duration) * 100}%`,
width: `${((clipEnd - clipStart) / duration) * 100}%`
}"
></div>
<!-- Event markers -->
<div
v-for="(event, idx) in sortedEvents"
:key="idx"
class="event-marker"
:style="{
left: `${getEventPosition(event)}%`,
backgroundColor: getEventColor(event)
}"
:title="`${formatEventTime(event)} - ${event.event_type}: ${event.description}`"
@click.stop="seekToEvent(event)"
@mouseenter="hoveredEvent = event"
@mouseleave="hoveredEvent = null"
></div>
<!-- Current time indicator -->
<div class="time-indicator" :style="{ left: `${(currentTime / duration) * 100}%` }"></div>
<div class="video-wrapper">
<!-- Video Player -->
<div class="video-container">
<div v-if="videoError" class="video-error">
<div class="error-icon"></div>
<p>{{ videoError }}</p>
</div>
<!-- Event tooltip -->
<div
v-if="hoveredEvent"
class="event-tooltip"
:style="{ left: `${getEventPosition(hoveredEvent)}%` }"
<video
v-else-if="videoUrl"
ref="videoRef"
class="video-player"
@loadedmetadata="onVideoLoaded"
@timeupdate="onVideoTimeUpdate"
@play="onVideoPlay"
@pause="onVideoPause"
@ended="onVideoEnded"
@error="onVideoError"
>
<div class="tooltip-time">{{ formatEventTime(hoveredEvent) }}</div>
<div class="tooltip-type">{{ hoveredEvent.event_type }}</div>
<div class="tooltip-desc">{{ hoveredEvent.description }}</div>
<source :src="videoUrl" type="video/mp4" />
Your browser does not support the video tag.
</video>
<div v-else class="video-loading">
<div class="spinner"></div>
<p>Loading video...</p>
</div>
<!-- Video Overlay Controls -->
<div class="video-overlay" v-if="videoUrl && !videoError">
<!-- Play/Pause Overlay -->
<div class="play-overlay" @click="togglePlay">
<div class="play-btn-large" v-if="!isPlaying">
<span></span>
</div>
</div>
</div>
</div>
<!-- Control buttons -->
<div class="controls-row">
<div class="controls-left">
<button class="control-btn" @click="togglePlay" :title="isPlaying ? 'Pause (Space)' : 'Play (Space)'">
<span v-if="isPlaying"></span>
<span v-else></span>
</button>
<button class="control-btn" @click="seekRelative(-10)" title="Back 10s (←)">
<span></span>
</button>
<button class="control-btn" @click="seekRelative(10)" title="Forward 10s (→)">
<span></span>
</button>
<div class="time-display">
<span>{{ formatDuration(currentTime) }}</span>
<span class="time-sep">/</span>
<span>{{ formatDuration(duration) }}</span>
</div>
</div>
<div class="controls-center">
<!-- Clip editing -->
<div class="clip-controls">
<button
class="clip-btn"
:class="{ active: clipStart !== null }"
@click="setClipStartPoint"
title="Set clip start (I)"
>
[I] Start
</button>
<button
class="clip-btn"
:class="{ active: clipEnd !== null }"
@click="setClipEndPoint"
title="Set clip end (O)"
>
[O] End
</button>
<span v-if="clipDuration" class="clip-duration">
{{ formatDuration(clipDuration) }}
</span>
<button
v-if="clipStart !== null || clipEnd !== null"
class="clip-btn clear"
@click="clearClipPoints"
>
Clear
</button>
<button
<!-- Video Controls -->
<div class="video-controls" v-if="videoUrl && !videoError">
<!-- Timeline with events -->
<div class="timeline-container">
<div class="timeline-bar" @click="onTimelineClick">
<!-- Progress bar -->
<div class="timeline-progress" :style="{ width: `${(currentTime / duration) * 100}%` }"></div>
<!-- Clip selection region -->
<div
v-if="clipStart !== null && clipEnd !== null"
class="clip-btn export"
@click="exportClip"
:disabled="isExporting"
>
{{ isExporting ? 'Exporting...' : 'Export Clip' }}
</button>
class="clip-region"
:style="{
left: `${(clipStart / duration) * 100}%`,
width: `${((clipEnd - clipStart) / duration) * 100}%`
}"
></div>
<!-- Event markers -->
<div
v-for="(event, idx) in sortedEvents"
:key="idx"
class="event-marker"
:style="{
left: `${getEventPosition(event)}%`,
backgroundColor: getEventColor(event)
}"
:title="`${formatEventTime(event)} - ${event.event_type}: ${event.description}`"
@click.stop="seekToEvent(event)"
@mouseenter="hoveredEvent = event"
@mouseleave="hoveredEvent = null"
></div>
<!-- Current time indicator -->
<div class="time-indicator" :style="{ left: `${(currentTime / duration) * 100}%` }"></div>
</div>
<!-- Event tooltip -->
<div
v-if="hoveredEvent"
class="event-tooltip"
:style="{ left: `${getEventPosition(hoveredEvent)}%` }"
>
<div class="tooltip-time">{{ formatEventTime(hoveredEvent) }}</div>
<div class="tooltip-type">{{ hoveredEvent.event_type }}</div>
<div class="tooltip-desc">{{ hoveredEvent.description }}</div>
</div>
</div>
<div class="controls-right">
<!-- Playback speed -->
<select
class="speed-select"
:value="playbackRate"
@change="setPlaybackRate(parseFloat(($event.target as HTMLSelectElement).value))"
>
<option value="0.25">0.25x</option>
<option value="0.5">0.5x</option>
<option value="0.75">0.75x</option>
<option value="1">1x</option>
<option value="1.25">1.25x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
</select>
<!-- Volume -->
<div class="volume-control">
<button class="control-btn" @click="toggleMute" :title="isMuted ? 'Unmute (M)' : 'Mute (M)'">
<span v-if="isMuted || volume === 0">🔇</span>
<span v-else-if="volume < 0.5">🔉</span>
<span v-else>🔊</span>
<!-- Control buttons -->
<div class="controls-row">
<div class="controls-left">
<button class="control-btn" @click="togglePlay" :title="isPlaying ? 'Pause (Space)' : 'Play (Space)'">
<span v-if="isPlaying"></span>
<span v-else></span>
</button>
<input
type="range"
class="volume-slider"
min="0"
max="1"
step="0.1"
:value="isMuted ? 0 : volume"
@input="setVolume(parseFloat(($event.target as HTMLInputElement).value))"
/>
<button class="control-btn" @click="seekRelative(-10)" title="Back 10s (←)">
<span></span>
</button>
<button class="control-btn" @click="seekRelative(10)" title="Forward 10s (→)">
<span></span>
</button>
<div class="time-display">
<span>{{ formatDuration(currentTime) }}</span>
<span class="time-sep">/</span>
<span>{{ formatDuration(duration) }}</span>
</div>
</div>
<button class="control-btn" @click="toggleFullscreen" title="Fullscreen (F)">
<span></span>
</button>
<div class="controls-center">
<!-- Clip editing -->
<div class="clip-controls">
<button
class="clip-btn"
:class="{ active: clipStart !== null }"
@click="setClipStartPoint"
title="Set clip start (I)"
>
[I] Start
</button>
<button
class="clip-btn"
:class="{ active: clipEnd !== null }"
@click="setClipEndPoint"
title="Set clip end (O)"
>
[O] End
</button>
<span v-if="clipDuration" class="clip-duration">
{{ formatDuration(clipDuration) }}
</span>
<button
v-if="clipStart !== null || clipEnd !== null"
class="clip-btn clear"
@click="clearClipPoints"
>
Clear
</button>
<button
v-if="clipStart !== null && clipEnd !== null"
class="clip-btn export"
@click="exportClip"
:disabled="isExporting"
>
{{ isExporting ? 'Exporting...' : 'Export Clip' }}
</button>
</div>
</div>
<div class="controls-right">
<!-- Playback speed -->
<select
class="speed-select"
:value="playbackRate"
@change="setPlaybackRate(parseFloat(($event.target as HTMLSelectElement).value))"
>
<option value="0.25">0.25x</option>
<option value="0.5">0.5x</option>
<option value="0.75">0.75x</option>
<option value="1">1x</option>
<option value="1.25">1.25x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
</select>
<!-- Volume -->
<div class="volume-control">
<button class="control-btn" @click="toggleMute" :title="isMuted ? 'Unmute (M)' : 'Mute (M)'">
<span v-if="isMuted || volume === 0">🔇</span>
<span v-else-if="volume < 0.5">🔉</span>
<span v-else>🔊</span>
</button>
<input
type="range"
class="volume-slider"
min="0"
max="1"
step="0.1"
:value="isMuted ? 0 : volume"
@input="setVolume(parseFloat(($event.target as HTMLInputElement).value))"
/>
</div>
<button class="control-btn" @click="toggleFullscreen" title="Fullscreen (F)">
<span></span>
</button>
</div>
</div>
</div>
</div>
@@ -974,16 +976,21 @@ onUnmounted(() => {
max-width: calc(100% - 350px);
}
.video-wrapper {
width: min(100%, calc(75vh * 16 / 9));
max-height: calc(75vh + 120px);
display: flex;
flex-direction: column;
}
.video-container {
position: relative;
background: #000;
aspect-ratio: 16/9;
max-height: 75vh;
width: min(100%, calc(75vh * 16 / 9));
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
}
.video-player {