Skip to content

Commit 77ecc05

Browse files
fix: audio player ui
1 parent d3a75c1 commit 77ecc05

File tree

3 files changed

+48
-42
lines changed

3 files changed

+48
-42
lines changed

src/components/AudioPlayer.vue

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,22 @@
22
<video @timeupdate="onTimeUpdate" @ended="onEnded" @loadedmetadata="onLoadedMetadata" ref="mediaRef" autoplay
33
hidden></video>
44

5-
<div v-if="track" class="bg-base-200 relative h-24">
6-
<!-- FIXME: ERROR on delete track -->
5+
<div v-if="track" class="bg-base-200 relative h-24 w-full">
76
<!-- Progress Bar and Needle-->
8-
<progress class="progress absolute top-0 h-1.5 w-full rounded-none transition-all hover:-top-1.5 hover:h-3"
9-
:value="playerState.currentTime" :max="playerState.duration" @click="onProgressClick"></progress>
7+
<progress class="progress absolute top-0 h-1.5 w-full rounded-none transition-all hover:-top-1.5 hover:h-3"
8+
:value="playerState.currentTime" :max="playerState.duration || 100" @click="onProgressClick"></progress>
109

1110
<div class="flex h-full w-full px-4 py-3">
12-
<div class="flex flex-1/3 gap-2">
13-
<img :src="getCoverUrl(track)" alt="cover" class="skeleton object-cover" />
14-
<div class="flex flex-col">
15-
<p class="font-bold">{{ track.title }}</p>
16-
<p class="text-base-content/70">{{ getArtist(track) }}</p>
11+
<div class="flex items-center gap-3 flex-1/3">
12+
<img :src="getCoverUrl(track)" alt="cover" class="object-cover skeleton size-18" />
13+
<div class="flex flex-col overflow-hidden">
14+
<p class="font-bold truncate" :title="track.title">{{ track.title }}</p>
15+
<p class="text-base-content/70 truncate" :title="getArtist(track)">{{ getArtist(track) }}</p>
1716
</div>
1817
</div>
1918

20-
<div class="flex flex-1/3 items-center justify-center gap-4">
21-
<button class="btn btn-ghost btn-circle">
19+
<div class="flex items-center justify-center gap-4 flex-1/3">
20+
<button class="btn btn-ghost btn-circle" @click="openListeningWidget">
2221
<i-mdi-playlist-play />
2322
</button>
2423
<button class="btn btn-ghost btn-circle" @click="nextTrack(-1)">
@@ -34,11 +33,11 @@
3433
</button>
3534
<PlayOrderSwitch></PlayOrderSwitch>
3635
</div>
37-
<div class="flex-1/3">
38-
<div>
39-
<button class="btn btn-ghost btn-circle" @click="openListeningWidget">
40-
<i-mdi-playlist-play />
41-
</button>
36+
<div class="flex items-center justify-end flex-1/3">
37+
<div class="flex items-center gap-2">
38+
<span class="text-sm">{{ formatSecs(playerState.currentTime) }}</span>
39+
<span class="text-sm">/</span>
40+
<span class="text-sm">{{ formatMillis(track.duration) }}</span>
4241
</div>
4342
</div>
4443
</div>
@@ -55,7 +54,7 @@ import {
5554
setCurrentTrack,
5655
setTrackUpdateCallback,
5756
} from "@/systems/player/listening-list"
58-
import { getArtist, getCoverUrl, replaceImageUrl } from "@/utils/utils"
57+
import { formatMillis, formatSecs, getArtist, getCoverUrl, replaceImageUrl } from "@/utils/utils"
5958
import Hls from "hls.js"
6059
import type { ErrorData } from "hls.js"
6160
import { Track } from "@/utils/types"
@@ -120,10 +119,10 @@ onMounted(() => {
120119
seek(details.seekTime!)
121120
})
122121
navigator.mediaSession.setActionHandler("seekforward", (details) => {
123-
seek(details.seekOffset ?? 10)
122+
seek(playerState.currentTime + (details.seekOffset ?? 10))
124123
})
125124
navigator.mediaSession.setActionHandler("seekbackward", (details) => {
126-
seek(details.seekOffset ?? -10)
125+
seek(playerState.currentTime - (details.seekOffset ?? 10))
127126
})
128127
navigator.mediaSession.setActionHandler("stop", () => {
129128
pause()
@@ -138,9 +137,10 @@ onMounted(() => {
138137
}
139138
140139
// set track update callbacks
141-
142-
setTrackUpdateCallback((_idx) => {
143-
loadSong()
140+
setTrackUpdateCallback((idx) => {
141+
if (idx >= 0) {
142+
loadSong()
143+
}
144144
})
145145
146146
// Initialize HLS player if supported
@@ -232,7 +232,8 @@ function seek(time: number) {
232232
if (!mediaRef.value || !isFinite(time)) {
233233
return
234234
}
235-
mediaRef.value.currentTime = time
235+
const safeTime = Math.max(0, Math.min(time, playerState.duration || time))
236+
mediaRef.value.currentTime = safeTime
236237
}
237238
238239
// 处理进度条点击事件以实现 Seek
@@ -269,6 +270,7 @@ function pause() {
269270
270271
async function loadSong(forceRefreshM3U8: boolean = false) {
271272
if (!mediaRef.value || !track.value) {
273+
playerState.loading = false
272274
return
273275
}
274276
@@ -297,11 +299,13 @@ async function loadSong(forceRefreshM3U8: boolean = false) {
297299
298300
hlsPlayer.value.once(Hls.Events.MANIFEST_PARSED, async () => {
299301
try {
300-
// restore load
301-
hlsPlayer.value!.startLoad(mediaRef.value!.currentTime)
302-
// 等待 HLS 解析完成后再播放
303-
await mediaRef.value!.play()
304-
playerState.paused = false
302+
if (mediaRef.value && track.value) {
303+
// restore load
304+
hlsPlayer.value!.startLoad(mediaRef.value!.currentTime)
305+
// 等待 HLS 解析完成后再播放
306+
await mediaRef.value!.play()
307+
playerState.paused = false
308+
}
305309
} catch (error) {
306310
console.error("HLS Play Failed:", error)
307311
playerState.paused = true
@@ -316,7 +320,6 @@ async function loadSong(forceRefreshM3U8: boolean = false) {
316320
})
317321
318322
// automatically loads after a few seconds
319-
320323
setTimeout(async () => {
321324
// TODO: abortable task
322325
await nextTrack()

src/components/ListeningWidget.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<template>
2-
<div> TODO: This UI still very poor and wip</div>
32
<VueFinalModal
43
class="flex items-center justify-center"
54
overlay-transition="vfm-fade"
65
content-transition="vfm-fade"
76
content-class="max-w-4xl modal-box opacity-100 max-h-[80vh] overflow-y-auto"
87
>
8+
9+
<div> TODO: This UI still very poor and wip</div>
910
<div class="mb-2 flex items-center gap-2">
1011
<button class="btn btn-primary" @click="removeSelected">
1112
{{ $t("cloudie.player.removeSelected") }}

src/utils/utils.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { path } from "@tauri-apps/api"
2-
import { SOCIAL_NETWORKS, Track, WebProfile } from "./types"
2+
import { SOCIAL_NETWORKS, type Track, type WebProfile } from "./types"
33

4-
export function capitalizeFirstLetter(str: string) {
4+
export function capitalizeFirstLetter(str: string): string {
55
return str.charAt(0).toUpperCase() + str.slice(1)
66
}
77

@@ -16,11 +16,11 @@ export function replaceImageUrl(
1616
| "1080x1080" // Cover / Avatar sizes
1717
| "1240x260"
1818
| "2480x520" = "500x500", // Visual sizes
19-
) {
19+
): string {
2020
return url.replace("-large", `-t${size}`)
2121
}
2222

23-
export function formatMillis(millis: number) {
23+
export function formatMillis(millis: number): string {
2424
const seconds = Math.floor(millis / 1000)
2525
const minutes = Math.floor(seconds / 60)
2626
const hours = Math.floor(minutes / 60)
@@ -31,8 +31,14 @@ export function formatMillis(millis: number) {
3131
return `${hours > 0 ? `${hours}:` : ""}${formattedMinutes}:${formattedSeconds}`
3232
}
3333

34+
export function formatSecs(seconds: number): string {
35+
const mins = Math.floor(seconds / 60)
36+
const secs = Math.floor(seconds % 60)
37+
return `${mins}:${secs.toString().padStart(2, "0")}`
38+
}
39+
3440
// from soundcloud json
35-
export function getNetworkName(profile: WebProfile) {
41+
export function getNetworkName(profile: WebProfile): string {
3642
if (profile.title) return profile.title
3743

3844
if (profile.network === "personal" || profile.network === "other") {
@@ -46,7 +52,7 @@ export function getNetworkName(profile: WebProfile) {
4652
return SOCIAL_NETWORKS[profile.network as keyof typeof SOCIAL_NETWORKS] || ""
4753
}
4854

49-
export function getNetworkClassName(profile: WebProfile) {
55+
export function getNetworkClassName(profile: WebProfile): string {
5056
return (profile.network || "").replace(/[^a-z]/g, "")
5157
}
5258

@@ -64,14 +70,10 @@ export function getCoverUrl(track: Track): string {
6470

6571
import * as fs from "@tauri-apps/plugin-fs"
6672

67-
export async function copyDir(
68-
srcDir: string,
69-
destDir: string,
70-
copyOptions?: fs.CopyFileOptions,
71-
): Promise<void> {
73+
export async function copyDir(srcDir: string, destDir: string, copyOptions?: fs.CopyFileOptions) {
7274
try {
7375
await fs.mkdir(destDir)
74-
} catch (e) {} // ignore if exists
76+
} catch (_) {} // ignore if exists
7577

7678
const entries = await fs.readDir(srcDir)
7779

0 commit comments

Comments
 (0)