Skip to content
This repository was archived by the owner on Apr 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions packages/nyx-player/src/components/AudioPlayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { computed, inject, onMounted, useTemplateRef } from 'vue'
import { useRefreshPlayStateTrigger } from '@/composables/useRefreshPlayStateTrigger'
import { usePlayingStore } from '@/stores/usePlayingStore'
import { PlayList } from '@/utils/metingapi/playlist'
import { ConcurrencyPool } from '@/utils/concurrency-pool'
import AudioController from './controller/AudioController.vue'
import PlayListTabs from './playlist/PlayListTabs.vue'
import AudioPreview from './preview/AudioPreview.vue'

const pool = new ConcurrencyPool(3) // 限制并发请求数为3

const props = defineProps<{
showPlayer: boolean
playlistURLs: { url: string, name: string }[]
Expand All @@ -36,12 +39,28 @@ onMounted(() => {
})

if (playingStore.playlists.length === 0) {
await Promise.all(props.playlistURLs.map(async (url, index) => {
const initPlaylist = async (url: { url: string; name: string }, index: number) => {
const playlist = new PlayList(url.url, url.name, index)
playingStore.playlists.push(playlist)
playlist.parserURL()
await playlist.fetchPlaylist()
}))
try {
playlist.parserURL()
await playlist.fetchPlaylist()
playingStore.playlists[index] = playlist
} catch (error) {
console.error(`Failed to initialize playlist ${url.name}:`, error)
// Create a placeholder playlist that can be retried later
playingStore.playlists[index] = playlist
}
}

// Pre-allocate array to maintain order
playingStore.playlists = new Array(props.playlistURLs.length)

// Queue all playlists for initialization through the concurrency pool
await Promise.allSettled(
props.playlistURLs.map((url, index) =>
pool.add(() => initPlaylist(url, index))
)
)
}

const updateCurrentTime = throttle((event: Event) => {
Expand Down
58 changes: 58 additions & 0 deletions packages/nyx-player/src/utils/concurrency-pool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
interface Task<T> {
fn: () => Promise<T>;
resolve: (value: T) => void;
reject: (error: any) => void;
}

export class ConcurrencyPool {
private readonly limit: number;
private running: number;
private queue: Task<any>[];

constructor(limit: number) {
this.limit = limit;
this.running = 0;
this.queue = [];
}

private async runTask<T>(task: Task<T>): Promise<void> {
this.running++;
try {
const result = await task.fn();
task.resolve(result);
} catch (error) {
task.reject(error);
} finally {
this.running--;
this.runNext();
}
}

private runNext(): void {
if (this.running < this.limit && this.queue.length > 0) {
const nextTask = this.queue.shift();
if (nextTask) {
this.runTask(nextTask);
}
}
}

async add<T>(fn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
const task: Task<T> = { fn, resolve, reject };
if (this.running < this.limit) {
this.runTask(task);
} else {
this.queue.push(task);
}
});
}

get active(): number {
return this.running;
}

get pending(): number {
return this.queue.length;
}
}
38 changes: 36 additions & 2 deletions packages/nyx-player/src/utils/metingapi/playlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,42 @@ export class PlayList {
if (!this.accessibleURL) {
throw new Error('Playlist URL is not accessible')
}
const res = await fetch(`${METING_API}?type=${this.accessibleURL.type}&id=${this.accessibleURL.id}&server=${this.accessibleURL.provider}`)
this.playlist = await res.json() as APIResponse[]

const maxRetries = 3
let lastError: Error | null = null

for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const res = await fetch(
`${METING_API}?type=${this.accessibleURL.type}&id=${this.accessibleURL.id}&server=${this.accessibleURL.provider}`,
{
headers: { 'Accept': 'application/json' },
signal: AbortSignal.timeout(10000) // 10s timeout
}
)

if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`)
}

const data = await res.json()
if (!Array.isArray(data)) {
throw new Error('Invalid playlist data received')
}

this.playlist = data as APIResponse[]
return
} catch (error) {
lastError = error as Error
console.warn(`Attempt ${attempt + 1} failed for playlist ${this.name}:`, error)
// exponential backoff
if (attempt < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, Math.min(1000 * Math.pow(2, attempt), 5000)))
}
}
}

throw new Error(`Failed to fetch playlist after ${maxRetries} attempts: ${lastError?.message}`)
}

getCurrentSong() {
Expand Down
2 changes: 2 additions & 0 deletions packages/nyx-player/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"lib": ["ES2021", "DOM", "DOM.Iterable"],
"target": "ES2020",

"paths": {
"@/*": ["./src/*"]
Expand Down
Loading