tauri-app: fix video control bar
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user