modified: src/components/TTSPlayer.vue
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user