modified: src/components/TTSPlayer.vue

This commit is contained in:
2026-04-12 11:42:22 +08:00
parent e0054d4cbc
commit 52ade88840
+64 -32
View File
@@ -5,23 +5,28 @@
<div class="tts-player__inner"> <div class="tts-player__inner">
<div class="tts-player__controls"> <div class="tts-player__controls">
<button class="tts-player__btn" @click="skipBackward" title="快退5秒"> <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"> <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="M11 12a7 7 0 0 0-7-7"/><path d="M11 12l-3-3"/> <path d="M1 4v6h6"/>
<text x="14" y="16" font-size="8" fill="currentColor" stroke="none">5</text> <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> </svg>
</button> </button>
<button class="tts-player__btn tts-player__btn--primary" @click="togglePlay" :title="isPlaying ? '暂停' : '播放'"> <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"> <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"/> <polygon points="5 3 19 12 5 21 5 3"/>
</svg> </svg>
<svg v-else width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none"> <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"/><rect x="14" y="4" width="4" height="16"/> <rect x="6" y="4" width="4" height="16" rx="1"/>
<rect x="14" y="4" width="4" height="16" rx="1"/>
</svg> </svg>
</Transition>
</button> </button>
<button class="tts-player__btn" @click="skipForward" title="快进5秒"> <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"> <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="M13 12a7 7 0 0 1 7-7"/><path d="M13 12l3-3"/> <path d="M23 4v6h-6"/>
<text x="4" y="16" font-size="8" fill="currentColor" stroke="none">5</text> <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> </svg>
</button> </button>
</div> </div>
@@ -75,7 +80,7 @@
</div> </div>
</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> </div>
</Transition> </Transition>
</Teleport> </Teleport>
@@ -135,8 +140,11 @@ function togglePlay() {
if (!audio) return if (!audio) return
if (isPlaying.value) { if (isPlaying.value) {
audio.pause() audio.pause()
isPlaying.value = false
} else { } else {
audio.play() audio.play().then(() => {
isPlaying.value = true
}).catch(() => {})
} }
} }
@@ -187,7 +195,11 @@ function onLoadedMetadata() {
duration.value = audio.duration || (props.durationMs / 1000) duration.value = audio.duration || (props.durationMs / 1000)
audio.volume = volume.value audio.volume = volume.value
audio.playbackRate = playbackRate.value audio.playbackRate = playbackRate.value
audio.play() audio.play().then(() => {
isPlaying.value = true
}).catch(() => {
isPlaying.value = false
})
} }
function onEnded() { function onEnded() {
@@ -195,15 +207,14 @@ function onEnded() {
currentTime.value = 0 currentTime.value = 0
} }
watch(isPlaying, (val) => { // 监听音频实际播放状态(用户可能通过其他方式暂停)
const audio = audioRef.value function onPlay() {
if (!audio) return isPlaying.value = true
if (val) { }
audio.play().catch(() => { isPlaying.value = false })
} else { function onPause() {
audio.pause() isPlaying.value = false
} }
})
watch(volume, (val) => { watch(volume, (val) => {
if (audioRef.value) { if (audioRef.value) {
@@ -220,7 +231,8 @@ watch(playbackRate, (val) => {
watch(() => props.visible, (val) => { watch(() => props.visible, (val) => {
if (val) { if (val) {
isPlaying.value = true // 打开播放器时重置状态,等待音频加载完成后才真正播放
isPlaying.value = false
currentTime.value = 0 currentTime.value = 0
} else { } else {
if (audioRef.value) { if (audioRef.value) {
@@ -245,16 +257,19 @@ onUnmounted(() => {
<style scoped> <style scoped>
.tts-player { .tts-player {
position: fixed; position: fixed;
bottom: 0; bottom: 16px;
left: 0; left: 50%;
right: 0; transform: translateX(-50%);
z-index: 99999; z-index: 99999;
padding: 8px 16px; max-width: 720px;
width: calc(100% - 32px);
padding: 12px 16px;
background: var(--panel-bg); background: var(--panel-bg);
backdrop-filter: blur(16px); backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(20px);
border-top: 1px solid var(--panel-border); border: 1px solid var(--panel-border);
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1); 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 { .tts-player__inner {
@@ -295,12 +310,18 @@ onUnmounted(() => {
height: 36px; height: 36px;
background: var(--btn-hover-bg); background: var(--btn-hover-bg);
color: var(--btn-hover-fg); color: var(--btn-hover-fg);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
} }
.tts-player__btn--primary:hover { .tts-player__btn--primary:hover {
transform: scale(1.08);
filter: brightness(1.1); filter: brightness(1.1);
} }
.tts-player__btn--primary:active {
transform: scale(0.95);
}
.tts-player__btn--close:hover { .tts-player__btn--close:hover {
background: rgba(220, 38, 38, 0.15); background: rgba(220, 38, 38, 0.15);
color: var(--danger-text); color: var(--danger-text);
@@ -421,12 +442,23 @@ onUnmounted(() => {
.tts-player-slide-enter-active, .tts-player-slide-enter-active,
.tts-player-slide-leave-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-enter-from,
.tts-player-slide-leave-to { .tts-player-slide-leave-to {
transform: translateY(100%); transform: translateX(-50%) translateY(24px);
opacity: 0; 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> </style>