modified: src/components/TTSPlayer.vue

This commit is contained in:
2026-04-12 11:42:22 +08:00
parent e0054d4cbc
commit 52ade88840

View File

@@ -3,28 +3,33 @@
<Transition name="tts-player-slide">
<div v-if="visible" class="tts-player">
<div class="tts-player__inner">
<div class="tts-player__controls">
<button class="tts-player__btn" @click="skipBackward" title="快退5秒">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 4v6h6"/><path d="M11 12a7 7 0 0 0-7-7"/><path d="M11 12l-3-3"/>
<text x="14" y="16" font-size="8" fill="currentColor" stroke="none">5</text>
</svg>
</button>
<button class="tts-player__btn tts-player__btn--primary" @click="togglePlay" :title="isPlaying ? '暂停' : '播放'">
<svg v-if="!isPlaying" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<div class="tts-player__controls">
<button class="tts-player__btn" @click="skipBackward" title="快退5秒">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 4v6h6"/>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
<text x="12" y="15" font-size="8" font-weight="600" fill="currentColor" stroke="none" text-anchor="middle">5</text>
</svg>
</button>
<button class="tts-player__btn tts-player__btn--primary" @click="togglePlay" :title="isPlaying ? '暂停' : '播放'">
<Transition name="icon-fade" mode="out-in">
<svg v-if="!isPlaying" key="play" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>
<svg v-else width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>
<svg v-else key="pause" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<rect x="6" y="4" width="4" height="16" rx="1"/>
<rect x="14" y="4" width="4" height="16" rx="1"/>
</svg>
</button>
<button class="tts-player__btn" @click="skipForward" title="快进5秒">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M23 4v6h-6"/><path d="M13 12a7 7 0 0 1 7-7"/><path d="M13 12l3-3"/>
<text x="4" y="16" font-size="8" fill="currentColor" stroke="none">5</text>
</svg>
</button>
</div>
</Transition>
</button>
<button class="tts-player__btn" @click="skipForward" title="快进5秒">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M23 4v6h-6"/>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
<text x="12" y="15" font-size="8" font-weight="600" fill="currentColor" stroke="none" text-anchor="middle">5</text>
</svg>
</button>
</div>
<div class="tts-player__progress">
<span class="tts-player__time">{{ currentTimeStr }}</span>
@@ -75,7 +80,7 @@
</div>
</div>
<audio ref="audioRef" :src="audioSrc" @timeupdate="onTimeUpdate" @ended="onEnded" @loadedmetadata="onLoadedMetadata"></audio>
<audio ref="audioRef" :src="audioSrc" @timeupdate="onTimeUpdate" @ended="onEnded" @loadedmetadata="onLoadedMetadata" @play="onPlay" @pause="onPause"></audio>
</div>
</Transition>
</Teleport>
@@ -135,8 +140,11 @@ function togglePlay() {
if (!audio) return
if (isPlaying.value) {
audio.pause()
isPlaying.value = false
} else {
audio.play()
audio.play().then(() => {
isPlaying.value = true
}).catch(() => {})
}
}
@@ -187,7 +195,11 @@ function onLoadedMetadata() {
duration.value = audio.duration || (props.durationMs / 1000)
audio.volume = volume.value
audio.playbackRate = playbackRate.value
audio.play()
audio.play().then(() => {
isPlaying.value = true
}).catch(() => {
isPlaying.value = false
})
}
function onEnded() {
@@ -195,15 +207,14 @@ function onEnded() {
currentTime.value = 0
}
watch(isPlaying, (val) => {
const audio = audioRef.value
if (!audio) return
if (val) {
audio.play().catch(() => { isPlaying.value = false })
} else {
audio.pause()
}
})
// 监听音频实际播放状态(用户可能通过其他方式暂停)
function onPlay() {
isPlaying.value = true
}
function onPause() {
isPlaying.value = false
}
watch(volume, (val) => {
if (audioRef.value) {
@@ -220,7 +231,8 @@ watch(playbackRate, (val) => {
watch(() => props.visible, (val) => {
if (val) {
isPlaying.value = true
// 打开播放器时重置状态,等待音频加载完成后才真正播放
isPlaying.value = false
currentTime.value = 0
} else {
if (audioRef.value) {
@@ -245,16 +257,19 @@ onUnmounted(() => {
<style scoped>
.tts-player {
position: fixed;
bottom: 0;
left: 0;
right: 0;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
z-index: 99999;
padding: 8px 16px;
max-width: 720px;
width: calc(100% - 32px);
padding: 12px 16px;
background: var(--panel-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-top: 1px solid var(--panel-border);
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--panel-border);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15), 0 2px 8px rgba(0, 0, 0, 0.1);
}
.tts-player__inner {
@@ -295,12 +310,18 @@ onUnmounted(() => {
height: 36px;
background: var(--btn-hover-bg);
color: var(--btn-hover-fg);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.tts-player__btn--primary:hover {
transform: scale(1.08);
filter: brightness(1.1);
}
.tts-player__btn--primary:active {
transform: scale(0.95);
}
.tts-player__btn--close:hover {
background: rgba(220, 38, 38, 0.15);
color: var(--danger-text);
@@ -421,12 +442,23 @@ onUnmounted(() => {
.tts-player-slide-enter-active,
.tts-player-slide-leave-active {
transition: transform 0.25s ease, opacity 0.2s ease;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s ease;
}
.tts-player-slide-enter-from,
.tts-player-slide-leave-to {
transform: translateY(100%);
transform: translateX(-50%) translateY(24px);
opacity: 0;
}
.icon-fade-enter-active,
.icon-fade-leave-active {
transition: all 0.15s ease;
}
.icon-fade-enter-from,
.icon-fade-leave-to {
opacity: 0;
transform: scale(0.7);
}
</style>