import axios from 'axios'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { Track } from './types'; const API_BASE_URL = 'https://traesite.umersoft.com:8975'; const PLAYLIST_LIST_PATH = '/api/zen_box/playlists'; const DEFAULT_PLAYLIST_FILE = 'playlist.json'; const PLAYLIST_SNAPSHOT_KEY_PREFIX = '@zen_box_playlist_snapshot:'; const SELECTED_PLAYLIST_KEY = '@zen_box_selected_playlist'; const RESUME_ENABLED_KEY = '@zen_box_resume_enabled'; const PLAYBACK_POSITION_KEY_PREFIX = '@zen_box_playback_position:'; const api = axios.create({ baseURL: API_BASE_URL, timeout: 10000, headers: { 'Content-Type': 'application/json', }, }); export const formatError = (error: any): string => { if (error.response) { return `服务器响应 ${error.response.status}: ${error.response.statusText}`; } else if (error.request) { return `服务器无响应: 无法连接到 ${API_BASE_URL}`; } else if (error.message) { return `请求错误: ${error.message}`; } else { return `未知错误`; } }; const normalizePlaylistFile = (fileName?: string): string => { return String(fileName || DEFAULT_PLAYLIST_FILE).trim() || DEFAULT_PLAYLIST_FILE; }; const buildPlaylistPath = (fileName?: string): string => { return `/tools/zen_box/${encodeURIComponent(normalizePlaylistFile(fileName))}`; }; const getPlaylistSnapshotKey = (fileName?: string): string => { return `${PLAYLIST_SNAPSHOT_KEY_PREFIX}${normalizePlaylistFile(fileName)}`; }; const getPlaybackPositionKey = (playlistFile?: string, trackSrc?: string): string => { return `${PLAYBACK_POSITION_KEY_PREFIX}${normalizePlaylistFile(playlistFile)}::${String(trackSrc || '').trim()}`; }; export const getPlaylistFiles = async (): Promise => { try { const response = await api.get<{ ok?: boolean; playlists?: string[] }>(PLAYLIST_LIST_PATH); const raw = Array.isArray(response.data?.playlists) ? response.data.playlists : []; const files = raw.map(item => String(item || '')).filter(Boolean); if (!files.includes(DEFAULT_PLAYLIST_FILE)) { files.unshift(DEFAULT_PLAYLIST_FILE); } return files; } catch (error) { console.error('读取歌单列表失败:', error); return [DEFAULT_PLAYLIST_FILE]; } }; export const getSavedSelectedPlaylist = async (): Promise => { try { const value = await AsyncStorage.getItem(SELECTED_PLAYLIST_KEY); return normalizePlaylistFile(value || DEFAULT_PLAYLIST_FILE); } catch (error) { console.error('读取当前歌单失败:', error); return DEFAULT_PLAYLIST_FILE; } }; export const saveSelectedPlaylist = async (fileName: string): Promise => { try { await AsyncStorage.setItem(SELECTED_PLAYLIST_KEY, normalizePlaylistFile(fileName)); } catch (error) { console.error('保存当前歌单失败:', error); } }; export const getResumePlaybackEnabled = async (): Promise => { try { const value = await AsyncStorage.getItem(RESUME_ENABLED_KEY); if (value === null) { return true; } return value === '1'; } catch (error) { console.error('读取断点续播设置失败:', error); return true; } }; export const saveResumePlaybackEnabled = async (enabled: boolean): Promise => { try { await AsyncStorage.setItem(RESUME_ENABLED_KEY, enabled ? '1' : '0'); } catch (error) { console.error('保存断点续播设置失败:', error); } }; export const getSavedPlaybackPosition = async (playlistFile?: string, trackSrc?: string): Promise => { try { const key = getPlaybackPositionKey(playlistFile, trackSrc); const value = await AsyncStorage.getItem(key); const seconds = Number(value || 0); return Number.isFinite(seconds) && seconds > 0 ? seconds : 0; } catch (error) { console.error('读取播放进度失败:', error); return 0; } }; export const savePlaybackPosition = async ( playlistFile: string, trackSrc: string, positionSeconds: number ): Promise => { try { const key = getPlaybackPositionKey(playlistFile, trackSrc); const safeSeconds = Math.max(0, Math.floor(positionSeconds || 0)); await AsyncStorage.setItem(key, String(safeSeconds)); } catch (error) { console.error('保存播放进度失败:', error); } }; export const clearPlaybackPosition = async (playlistFile: string, trackSrc: string): Promise => { try { const key = getPlaybackPositionKey(playlistFile, trackSrc); await AsyncStorage.removeItem(key); } catch (error) { console.error('清除播放进度失败:', error); } }; export const getPlaylist = async (fileName?: string): Promise => { try { const response = await api.get(buildPlaylistPath(fileName)); return response.data; } catch (error) { throw new Error(formatError(error)); } }; const isValidPlaylist = (data: unknown): data is Track[] => { return Array.isArray(data) && data.every((item) => { if (!item || typeof item !== 'object') { return false; } const track = item as Track; return typeof track.title === 'string' && typeof track.artist === 'string' && typeof track.src === 'string'; }); }; export const savePlaylistSnapshot = async (tracks: Track[], fileName?: string): Promise => { try { await AsyncStorage.setItem(getPlaylistSnapshotKey(fileName), JSON.stringify(tracks)); } catch (error) { console.error('保存歌单快照失败:', error); } }; export const getPlaylistSnapshot = async (fileName?: string): Promise => { try { const raw = await AsyncStorage.getItem(getPlaylistSnapshotKey(fileName)); if (!raw) { return []; } const parsed: unknown = JSON.parse(raw); return isValidPlaylist(parsed) ? parsed : []; } catch (error) { console.error('读取歌单快照失败:', error); return []; } }; export const getPlaylistWithFallback = async (fileName?: string): Promise<{ tracks: Track[]; source: 'remote' | 'snapshot'; fallbackReason?: string; playlistFile: string; }> => { const normalizedFile = normalizePlaylistFile(fileName); try { const tracks = await getPlaylist(normalizedFile); await savePlaylistSnapshot(tracks, normalizedFile); return { tracks, source: 'remote', playlistFile: normalizedFile }; } catch (error) { const fallbackTracks = await getPlaylistSnapshot(normalizedFile); if (fallbackTracks.length > 0) { return { tracks: fallbackTracks, source: 'snapshot', fallbackReason: error instanceof Error ? error.message : '远程歌单加载失败', playlistFile: normalizedFile, }; } throw error; } };