refactor(logging): implement unified Logger system to replace console calls

- Add Logger utility with tagged output and environment-based control
- Configure Babel to remove console calls in production builds
- Replace all console.* calls across stores, services, and components with Logger
- Enable development-only logging with formatted output and component tags
- Optimize production builds by eliminating all logging code
This commit is contained in:
zimplexing
2025-08-15 22:57:38 +08:00
parent 836285dbd5
commit e57466c8c1
25 changed files with 404 additions and 200 deletions

View File

@@ -15,6 +15,9 @@ import { useUpdateStore, initUpdateStore } from "@/stores/updateStore";
import { UpdateModal } from "@/components/UpdateModal"; import { UpdateModal } from "@/components/UpdateModal";
import { UPDATE_CONFIG } from "@/constants/UpdateConfig"; import { UPDATE_CONFIG } from "@/constants/UpdateConfig";
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('RootLayout');
// Prevent the splash screen from auto-hiding before asset loading is complete. // Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync(); SplashScreen.preventAutoHideAsync();
@@ -48,7 +51,7 @@ export default function RootLayout() {
if (loaded || error) { if (loaded || error) {
SplashScreen.hideAsync(); SplashScreen.hideAsync();
if (error) { if (error) {
console.warn(`Error in loading fonts: ${error}`); logger.warn(`Error in loading fonts: ${error}`);
} }
} }
}, [loaded, error]); }, [loaded, error]);

View File

@@ -17,11 +17,14 @@ import Toast from "react-native-toast-message";
import usePlayerStore, { selectCurrentEpisode } from "@/stores/playerStore"; import usePlayerStore, { selectCurrentEpisode } from "@/stores/playerStore";
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
import { useVideoHandlers } from "@/hooks/useVideoHandlers"; import { useVideoHandlers } from "@/hooks/useVideoHandlers";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('PlayScreen');
// 优化的加载动画组件 // 优化的加载动画组件
const LoadingContainer = memo( const LoadingContainer = memo(
({ style, currentEpisode }: { style: any; currentEpisode: { url: string; title: string } | undefined }) => { ({ style, currentEpisode }: { style: any; currentEpisode: { url: string; title: string } | undefined }) => {
console.info( logger.info(
`[PERF] Video component NOT rendered - waiting for valid URL. currentEpisode: ${!!currentEpisode}, url: ${ `[PERF] Video component NOT rendered - waiting for valid URL. currentEpisode: ${!!currentEpisode}, url: ${
currentEpisode?.url ? "exists" : "missing" currentEpisode?.url ? "exists" : "missing"
}` }`
@@ -130,21 +133,21 @@ export default function PlayScreen() {
useEffect(() => { useEffect(() => {
const perfStart = performance.now(); const perfStart = performance.now();
console.info(`[PERF] PlayScreen useEffect START - source: ${source}, id: ${id}, title: ${title}`); logger.info(`[PERF] PlayScreen useEffect START - source: ${source}, id: ${id}, title: ${title}`);
setVideoRef(videoRef); setVideoRef(videoRef);
if (source && id && title) { if (source && id && title) {
console.info(`[PERF] Calling loadVideo with episodeIndex: ${episodeIndex}, position: ${position}`); logger.info(`[PERF] Calling loadVideo with episodeIndex: ${episodeIndex}, position: ${position}`);
loadVideo({ source, id, episodeIndex, position, title }); loadVideo({ source, id, episodeIndex, position, title });
} else { } else {
console.info(`[PERF] Missing required params - source: ${!!source}, id: ${!!id}, title: ${!!title}`); logger.info(`[PERF] Missing required params - source: ${!!source}, id: ${!!id}, title: ${!!title}`);
} }
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] PlayScreen useEffect END - took ${(perfEnd - perfStart).toFixed(2)}ms`); logger.info(`[PERF] PlayScreen useEffect END - took ${(perfEnd - perfStart).toFixed(2)}ms`);
return () => { return () => {
console.info(`[PERF] PlayScreen unmounting - calling reset()`); logger.info(`[PERF] PlayScreen unmounting - calling reset()`);
reset(); // Reset state when component unmounts reset(); // Reset state when component unmounts
}; };
}, [episodeIndex, source, position, setVideoRef, reset, loadVideo, id, title]); }, [episodeIndex, source, position, setVideoRef, reset, loadVideo, id, title]);

View File

@@ -18,6 +18,9 @@ import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles";
import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation"; import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation";
import ResponsiveHeader from "@/components/navigation/ResponsiveHeader"; import ResponsiveHeader from "@/components/navigation/ResponsiveHeader";
import { DeviceUtils } from "@/utils/DeviceUtils"; import { DeviceUtils } from "@/utils/DeviceUtils";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('SearchScreen');
export default function SearchScreen() { export default function SearchScreen() {
const [keyword, setKeyword] = useState(""); const [keyword, setKeyword] = useState("");
@@ -37,7 +40,7 @@ export default function SearchScreen() {
useEffect(() => { useEffect(() => {
if (lastMessage && targetPage === 'search') { if (lastMessage && targetPage === 'search') {
console.log("Received remote input:", lastMessage); logger.debug("Received remote input:", lastMessage);
const realMessage = lastMessage.split("_")[0]; const realMessage = lastMessage.split("_")[0];
setKeyword(realMessage); setKeyword(realMessage);
handleSearch(realMessage); handleSearch(realMessage);
@@ -72,7 +75,7 @@ export default function SearchScreen() {
} }
} catch (err) { } catch (err) {
setError("搜索失败,请稍后重试。"); setError("搜索失败,请稍后重试。");
console.info("Search failed:", err); logger.info("Search failed:", err);
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@@ -1,7 +1,15 @@
module.exports = function (api) { module.exports = function (api) {
api.cache(true); api.cache(true);
const plugins = [];
// 在生产环境移除console调用以优化性能
if (process.env.NODE_ENV === 'production') {
plugins.push('transform-remove-console');
}
return { return {
presets: ['babel-preset-expo'], presets: ['babel-preset-expo'],
plugins: [], plugins,
}; };
}; };

View File

@@ -8,6 +8,9 @@ import { ThemedText } from "@/components/ThemedText";
import { Colors } from "@/constants/Colors"; import { Colors } from "@/constants/Colors";
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
import { DeviceUtils } from "@/utils/DeviceUtils"; import { DeviceUtils } from "@/utils/DeviceUtils";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('ResponsiveVideoCard');
interface VideoCardProps extends React.ComponentProps<typeof TouchableOpacity> { interface VideoCardProps extends React.ComponentProps<typeof TouchableOpacity> {
id: string; id: string;
@@ -138,7 +141,7 @@ const ResponsiveVideoCard = forwardRef<View, VideoCardProps>(
router.replace("/"); router.replace("/");
} }
} catch (error) { } catch (error) {
console.info("Failed to delete play record:", error); logger.info("Failed to delete play record:", error);
Alert.alert("错误", "删除观看记录失败,请重试"); Alert.alert("错误", "删除观看记录失败,请重试");
} }
}, },

View File

@@ -3,13 +3,16 @@ import { View, Text, StyleSheet, Modal, FlatList } from "react-native";
import { StyledButton } from "./StyledButton"; import { StyledButton } from "./StyledButton";
import useDetailStore from "@/stores/detailStore"; import useDetailStore from "@/stores/detailStore";
import usePlayerStore from "@/stores/playerStore"; import usePlayerStore from "@/stores/playerStore";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('SourceSelectionModal');
export const SourceSelectionModal: React.FC = () => { export const SourceSelectionModal: React.FC = () => {
const { showSourceModal, setShowSourceModal, loadVideo, currentEpisodeIndex, status } = usePlayerStore(); const { showSourceModal, setShowSourceModal, loadVideo, currentEpisodeIndex, status } = usePlayerStore();
const { searchResults, detail, setDetail } = useDetailStore(); const { searchResults, detail, setDetail } = useDetailStore();
const onSelectSource = (index: number) => { const onSelectSource = (index: number) => {
console.log("onSelectSource", index, searchResults[index].source, detail?.source); logger.debug("onSelectSource", index, searchResults[index].source, detail?.source);
if (searchResults[index].source !== detail?.source) { if (searchResults[index].source !== detail?.source) {
const newDetail = searchResults[index]; const newDetail = searchResults[index];
setDetail(newDetail); setDetail(newDetail);

View File

@@ -8,6 +8,9 @@ import { ThemedText } from "@/components/ThemedText";
import { Colors } from "@/constants/Colors"; import { Colors } from "@/constants/Colors";
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
import { DeviceUtils } from "@/utils/DeviceUtils"; import { DeviceUtils } from "@/utils/DeviceUtils";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('VideoCardMobile');
interface VideoCardMobileProps extends React.ComponentProps<typeof TouchableOpacity> { interface VideoCardMobileProps extends React.ComponentProps<typeof TouchableOpacity> {
id: string; id: string;
@@ -97,7 +100,7 @@ const VideoCardMobile = forwardRef<View, VideoCardMobileProps>(
await PlayRecordManager.remove(source, id); await PlayRecordManager.remove(source, id);
onRecordDeleted?.(); onRecordDeleted?.();
} catch (error) { } catch (error) {
console.info("Failed to delete play record:", error); logger.info("Failed to delete play record:", error);
Alert.alert("错误", "删除观看记录失败,请重试"); Alert.alert("错误", "删除观看记录失败,请重试");
} }
}, },

View File

@@ -8,6 +8,9 @@ import { ThemedText } from "@/components/ThemedText";
import { Colors } from "@/constants/Colors"; import { Colors } from "@/constants/Colors";
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout"; import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
import { DeviceUtils } from "@/utils/DeviceUtils"; import { DeviceUtils } from "@/utils/DeviceUtils";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('VideoCardTablet');
interface VideoCardTabletProps extends React.ComponentProps<typeof TouchableOpacity> { interface VideoCardTabletProps extends React.ComponentProps<typeof TouchableOpacity> {
id: string; id: string;
@@ -119,7 +122,7 @@ const VideoCardTablet = forwardRef<View, VideoCardTabletProps>(
await PlayRecordManager.remove(source, id); await PlayRecordManager.remove(source, id);
onRecordDeleted?.(); onRecordDeleted?.();
} catch (error) { } catch (error) {
console.info("Failed to delete play record:", error); logger.info("Failed to delete play record:", error);
Alert.alert("错误", "删除观看记录失败,请重试"); Alert.alert("错误", "删除观看记录失败,请重试");
} }
}, },

View File

@@ -6,6 +6,9 @@ import { PlayRecordManager } from "@/services/storage";
import { API } from "@/services/api"; import { API } from "@/services/api";
import { ThemedText } from "@/components/ThemedText"; import { ThemedText } from "@/components/ThemedText";
import { Colors } from "@/constants/Colors"; import { Colors } from "@/constants/Colors";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('VideoCardTV');
interface VideoCardProps extends React.ComponentProps<typeof TouchableOpacity> { interface VideoCardProps extends React.ComponentProps<typeof TouchableOpacity> {
id: string; id: string;
@@ -131,7 +134,7 @@ const VideoCard = forwardRef<View, VideoCardProps>(
router.replace("/"); router.replace("/");
} }
} catch (error) { } catch (error) {
console.info("Failed to delete play record:", error); logger.info("Failed to delete play record:", error);
Alert.alert("错误", "删除观看记录失败,请重试"); Alert.alert("错误", "删除观看记录失败,请重试");
} }
}, },

View File

@@ -9,7 +9,7 @@
"ios": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo run:ios", "ios": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo run:ios",
"prebuild": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo prebuild --clean && yarn copy-config", "prebuild": "EXPO_TV=1 EXPO_USE_METRO_WORKSPACE_ROOT=1 expo prebuild --clean && yarn copy-config",
"copy-config": "cp -r xml/* android/app/src/*", "copy-config": "cp -r xml/* android/app/src/*",
"build": "EXPO_TV=1 yarn prebuild && cd android && ./gradlew assembleRelease", "build": "NODE_ENV=production EXPO_TV=1 yarn prebuild && cd android && ./gradlew assembleRelease",
"build-debug": "cd android && ./gradlew assembleDebug", "build-debug": "cd android && ./gradlew assembleDebug",
"test": "jest --watchAll", "test": "jest --watchAll",
"test-ci": "jest --ci --coverage --no-cache", "test-ci": "jest --ci --coverage --no-cache",
@@ -67,6 +67,7 @@
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/react": "~18.2.45", "@types/react": "~18.2.45",
"@types/react-test-renderer": "^18.0.7", "@types/react-test-renderer": "^18.0.7",
"babel-plugin-transform-remove-console": "^6.9.4",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-expo": "~7.1.2", "eslint-config-expo": "~7.1.2",
"jest": "^29.2.1", "jest": "^29.2.1",

View File

@@ -1,4 +1,8 @@
import Logger from '@/utils/Logger';
const logger = Logger.withTag('API');
// region: --- Interface Definitions --- // region: --- Interface Definitions ---
export interface DoubanItem { export interface DoubanItem {
title: string; title: string;
@@ -215,13 +219,13 @@ export class API {
// 添加安全检查 // 添加安全检查
if (!config || !config.Config.SourceConfig) { if (!config || !config.Config.SourceConfig) {
console.warn('API response missing SourceConfig:', config); logger.warn('API response missing SourceConfig:', config);
return []; return [];
} }
// 确保 SourceConfig 是数组 // 确保 SourceConfig 是数组
if (!Array.isArray(config.Config.SourceConfig)) { if (!Array.isArray(config.Config.SourceConfig)) {
console.warn('SourceConfig is not an array:', config.Config.SourceConfig); logger.warn('SourceConfig is not an array:', config.Config.SourceConfig);
return []; return [];
} }

View File

@@ -1,3 +1,7 @@
import Logger from '@/utils/Logger';
const logger = Logger.withTag('M3U');
export interface Channel { export interface Channel {
id: string; id: string;
name: string; name: string;
@@ -59,7 +63,7 @@ export const fetchAndParseM3u = async (m3uUrl: string): Promise<Channel[]> => {
const m3uText = await response.text(); const m3uText = await response.text();
return parseM3U(m3uText); return parseM3U(m3uText);
} catch (error) { } catch (error) {
console.info("Error fetching or parsing M3U:", error); logger.info("Error fetching or parsing M3U:", error);
return []; // Return empty array on error return []; // Return empty array on error
} }
}; };

View File

@@ -1,3 +1,7 @@
import Logger from '@/utils/Logger';
const logger = Logger.withTag('M3U8');
interface CacheEntry { interface CacheEntry {
resolution: string | null; resolution: string | null;
timestamp: number; timestamp: number;
@@ -11,18 +15,18 @@ export const getResolutionFromM3U8 = async (
signal?: AbortSignal signal?: AbortSignal
): Promise<string | null> => { ): Promise<string | null> => {
const perfStart = performance.now(); const perfStart = performance.now();
console.info(`[PERF] M3U8 resolution detection START - url: ${url.substring(0, 100)}...`); logger.info(`[PERF] M3U8 resolution detection START - url: ${url.substring(0, 100)}...`);
// 1. Check cache first // 1. Check cache first
const cachedEntry = resolutionCache[url]; const cachedEntry = resolutionCache[url];
if (cachedEntry && Date.now() - cachedEntry.timestamp < CACHE_DURATION) { if (cachedEntry && Date.now() - cachedEntry.timestamp < CACHE_DURATION) {
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] M3U8 resolution detection CACHED - took ${(perfEnd - perfStart).toFixed(2)}ms, resolution: ${cachedEntry.resolution}`); logger.info(`[PERF] M3U8 resolution detection CACHED - took ${(perfEnd - perfStart).toFixed(2)}ms, resolution: ${cachedEntry.resolution}`);
return cachedEntry.resolution; return cachedEntry.resolution;
} }
if (!url.toLowerCase().endsWith(".m3u8")) { if (!url.toLowerCase().endsWith(".m3u8")) {
console.info(`[PERF] M3U8 resolution detection SKIPPED - not M3U8 file`); logger.info(`[PERF] M3U8 resolution detection SKIPPED - not M3U8 file`);
return null; return null;
} }
@@ -30,7 +34,7 @@ export const getResolutionFromM3U8 = async (
const fetchStart = performance.now(); const fetchStart = performance.now();
const response = await fetch(url, { signal }); const response = await fetch(url, { signal });
const fetchEnd = performance.now(); const fetchEnd = performance.now();
console.info(`[PERF] M3U8 fetch took ${(fetchEnd - fetchStart).toFixed(2)}ms, status: ${response.status}`); logger.info(`[PERF] M3U8 fetch took ${(fetchEnd - fetchStart).toFixed(2)}ms, status: ${response.status}`);
if (!response.ok) { if (!response.ok) {
return null; return null;
@@ -56,7 +60,7 @@ export const getResolutionFromM3U8 = async (
} }
const parseEnd = performance.now(); const parseEnd = performance.now();
console.info(`[PERF] M3U8 parsing took ${(parseEnd - parseStart).toFixed(2)}ms, lines: ${lines.length}`); logger.info(`[PERF] M3U8 parsing took ${(parseEnd - parseStart).toFixed(2)}ms, lines: ${lines.length}`);
// 2. Store result in cache // 2. Store result in cache
resolutionCache[url] = { resolutionCache[url] = {
@@ -65,12 +69,12 @@ export const getResolutionFromM3U8 = async (
}; };
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] M3U8 resolution detection COMPLETE - took ${(perfEnd - perfStart).toFixed(2)}ms, resolution: ${resolutionString}`); logger.info(`[PERF] M3U8 resolution detection COMPLETE - took ${(perfEnd - perfStart).toFixed(2)}ms, resolution: ${resolutionString}`);
return resolutionString; return resolutionString;
} catch (error) { } catch (error) {
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] M3U8 resolution detection ERROR - took ${(perfEnd - perfStart).toFixed(2)}ms, error: ${error}`); logger.info(`[PERF] M3U8 resolution detection ERROR - took ${(perfEnd - perfStart).toFixed(2)}ms, error: ${error}`);
return null; return null;
} }
}; };

View File

@@ -1,4 +1,7 @@
import TCPHttpServer from "./tcpHttpServer"; import TCPHttpServer from "./tcpHttpServer";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('RemoteControl');
const getRemotePageHTML = () => { const getRemotePageHTML = () => {
return ` return `
@@ -25,7 +28,7 @@ const getRemotePageHTML = () => {
</div> </div>
<script> <script>
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
fetch('/handshake', { method: 'POST' }).catch(console.info); fetch('/handshake', { method: 'POST' }).catch(err => logger.info('Handshake failed:', err));
}); });
function send() { function send() {
const input = document.getElementById("text"); const input = document.getElementById("text");
@@ -36,7 +39,7 @@ const getRemotePageHTML = () => {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: value }) body: JSON.stringify({ message: value })
}) })
.catch(err => console.info(err)); .catch(err => logger.info('Message send failed:', err));
input.value = ''; input.value = '';
} }
} }
@@ -58,7 +61,7 @@ class RemoteControlService {
private setupRequestHandler() { private setupRequestHandler() {
this.httpServer.setRequestHandler((request) => { this.httpServer.setRequestHandler((request) => {
console.log("[RemoteControl] Received request:", request.method, request.url); logger.debug("[RemoteControl] Received request:", request.method, request.url);
try { try {
if (request.method === "GET" && request.url === "/") { if (request.method === "GET" && request.url === "/") {
@@ -80,7 +83,7 @@ class RemoteControlService {
body: JSON.stringify({ status: "ok" }), body: JSON.stringify({ status: "ok" }),
}; };
} catch (parseError) { } catch (parseError) {
console.info("[RemoteControl] Failed to parse message body:", parseError); logger.info("[RemoteControl] Failed to parse message body:", parseError);
return { return {
statusCode: 400, statusCode: 400,
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -102,7 +105,7 @@ class RemoteControlService {
}; };
} }
} catch (error) { } catch (error) {
console.info("[RemoteControl] Request handler error:", error); logger.info("[RemoteControl] Request handler error:", error);
return { return {
statusCode: 500, statusCode: 500,
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -118,20 +121,20 @@ class RemoteControlService {
} }
public async startServer(): Promise<string> { public async startServer(): Promise<string> {
console.log("[RemoteControl] Attempting to start server..."); logger.debug("[RemoteControl] Attempting to start server...");
try { try {
const url = await this.httpServer.start(); const url = await this.httpServer.start();
console.log(`[RemoteControl] Server started successfully at: ${url}`); logger.debug(`[RemoteControl] Server started successfully at: ${url}`);
return url; return url;
} catch (error) { } catch (error) {
console.info("[RemoteControl] Failed to start server:", error); logger.info("[RemoteControl] Failed to start server:", error);
throw new Error(error instanceof Error ? error.message : "Failed to start server"); throw new Error(error instanceof Error ? error.message : "Failed to start server");
} }
} }
public stopServer() { public stopServer() {
console.log("[RemoteControl] Stopping server..."); logger.debug("[RemoteControl] Stopping server...");
this.httpServer.stop(); this.httpServer.stop();
} }

View File

@@ -1,6 +1,9 @@
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import { api, PlayRecord as ApiPlayRecord, Favorite as ApiFavorite } from "./api"; import { api, PlayRecord as ApiPlayRecord, Favorite as ApiFavorite } from "./api";
import { storageConfig } from "./storageConfig"; import { storageConfig } from "./storageConfig";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('Storage');
// --- Storage Keys --- // --- Storage Keys ---
const STORAGE_KEYS = { const STORAGE_KEYS = {
@@ -53,20 +56,20 @@ export class PlayerSettingsManager {
const data = await AsyncStorage.getItem(STORAGE_KEYS.PLAYER_SETTINGS); const data = await AsyncStorage.getItem(STORAGE_KEYS.PLAYER_SETTINGS);
return data ? JSON.parse(data) : {}; return data ? JSON.parse(data) : {};
} catch (error) { } catch (error) {
console.info("Failed to get all player settings:", error); logger.info("Failed to get all player settings:", error);
return {}; return {};
} }
} }
static async get(source: string, id: string): Promise<PlayerSettings | null> { static async get(source: string, id: string): Promise<PlayerSettings | null> {
const perfStart = performance.now(); const perfStart = performance.now();
console.info(`[PERF] PlayerSettingsManager.get START - source: ${source}, id: ${id}`); logger.info(`[PERF] PlayerSettingsManager.get START - source: ${source}, id: ${id}`);
const allSettings = await this.getAll(); const allSettings = await this.getAll();
const result = allSettings[generateKey(source, id)] || null; const result = allSettings[generateKey(source, id)] || null;
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] PlayerSettingsManager.get END - took ${(perfEnd - perfStart).toFixed(2)}ms, found: ${!!result}`); logger.info(`[PERF] PlayerSettingsManager.get END - took ${(perfEnd - perfStart).toFixed(2)}ms, found: ${!!result}`);
return result; return result;
} }
@@ -107,7 +110,7 @@ export class FavoriteManager {
const data = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES); const data = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);
return data ? JSON.parse(data) : {}; return data ? JSON.parse(data) : {};
} catch (error) { } catch (error) {
console.info("Failed to get all local favorites:", error); logger.info("Failed to get all local favorites:", error);
return {}; return {};
} }
} }
@@ -175,7 +178,7 @@ export class PlayRecordManager {
static async getAll(): Promise<Record<string, PlayRecord>> { static async getAll(): Promise<Record<string, PlayRecord>> {
const perfStart = performance.now(); const perfStart = performance.now();
const storageType = this.getStorageType(); const storageType = this.getStorageType();
console.info(`[PERF] PlayRecordManager.getAll START - storageType: ${storageType}`); logger.info(`[PERF] PlayRecordManager.getAll START - storageType: ${storageType}`);
let apiRecords: Record<string, PlayRecord> = {}; let apiRecords: Record<string, PlayRecord> = {};
if (storageType === "localstorage") { if (storageType === "localstorage") {
@@ -183,17 +186,17 @@ export class PlayRecordManager {
const data = await AsyncStorage.getItem(STORAGE_KEYS.PLAY_RECORDS); const data = await AsyncStorage.getItem(STORAGE_KEYS.PLAY_RECORDS);
apiRecords = data ? JSON.parse(data) : {}; apiRecords = data ? JSON.parse(data) : {};
} catch (error) { } catch (error) {
console.info("Failed to get all local play records:", error); logger.info("Failed to get all local play records:", error);
return {}; return {};
} }
} else { } else {
const apiStart = performance.now(); const apiStart = performance.now();
console.info(`[PERF] API getPlayRecords START`); logger.info(`[PERF] API getPlayRecords START`);
apiRecords = await api.getPlayRecords(); apiRecords = await api.getPlayRecords();
const apiEnd = performance.now(); const apiEnd = performance.now();
console.info(`[PERF] API getPlayRecords END - took ${(apiEnd - apiStart).toFixed(2)}ms, records: ${Object.keys(apiRecords).length}`); logger.info(`[PERF] API getPlayRecords END - took ${(apiEnd - apiStart).toFixed(2)}ms, records: ${Object.keys(apiRecords).length}`);
} }
const localSettings = await PlayerSettingsManager.getAll(); const localSettings = await PlayerSettingsManager.getAll();
@@ -206,7 +209,7 @@ export class PlayRecordManager {
} }
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] PlayRecordManager.getAll END - took ${(perfEnd - perfStart).toFixed(2)}ms, total records: ${Object.keys(mergedRecords).length}`); logger.info(`[PERF] PlayRecordManager.getAll END - took ${(perfEnd - perfStart).toFixed(2)}ms, total records: ${Object.keys(mergedRecords).length}`);
return mergedRecords; return mergedRecords;
} }
@@ -232,13 +235,13 @@ export class PlayRecordManager {
const perfStart = performance.now(); const perfStart = performance.now();
const key = generateKey(source, id); const key = generateKey(source, id);
const storageType = this.getStorageType(); const storageType = this.getStorageType();
console.info(`[PERF] PlayRecordManager.get START - source: ${source}, id: ${id}, storageType: ${storageType}`); logger.info(`[PERF] PlayRecordManager.get START - source: ${source}, id: ${id}, storageType: ${storageType}`);
const records = await this.getAll(); const records = await this.getAll();
const result = records[key] || null; const result = records[key] || null;
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] PlayRecordManager.get END - took ${(perfEnd - perfStart).toFixed(2)}ms, found: ${!!result}`); logger.info(`[PERF] PlayRecordManager.get END - took ${(perfEnd - perfStart).toFixed(2)}ms, found: ${!!result}`);
return result; return result;
} }
@@ -279,7 +282,7 @@ export class SearchHistoryManager {
const data = await AsyncStorage.getItem(STORAGE_KEYS.SEARCH_HISTORY); const data = await AsyncStorage.getItem(STORAGE_KEYS.SEARCH_HISTORY);
return data ? JSON.parse(data) : []; return data ? JSON.parse(data) : [];
} catch (error) { } catch (error) {
console.info("Failed to get local search history:", error); logger.info("Failed to get local search history:", error);
return []; return [];
} }
} }
@@ -324,7 +327,7 @@ export class SettingsManager {
const data = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS); const data = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS);
return data ? { ...defaultSettings, ...JSON.parse(data) } : defaultSettings; return data ? { ...defaultSettings, ...JSON.parse(data) } : defaultSettings;
} catch (error) { } catch (error) {
console.info("Failed to get settings:", error); logger.info("Failed to get settings:", error);
return defaultSettings; return defaultSettings;
} }
} }
@@ -347,7 +350,7 @@ export class LoginCredentialsManager {
const data = await AsyncStorage.getItem(STORAGE_KEYS.LOGIN_CREDENTIALS); const data = await AsyncStorage.getItem(STORAGE_KEYS.LOGIN_CREDENTIALS);
return data ? JSON.parse(data) : null; return data ? JSON.parse(data) : null;
} catch (error) { } catch (error) {
console.info("Failed to get login credentials:", error); logger.info("Failed to get login credentials:", error);
return null; return null;
} }
} }
@@ -356,7 +359,7 @@ export class LoginCredentialsManager {
try { try {
await AsyncStorage.setItem(STORAGE_KEYS.LOGIN_CREDENTIALS, JSON.stringify(credentials)); await AsyncStorage.setItem(STORAGE_KEYS.LOGIN_CREDENTIALS, JSON.stringify(credentials));
} catch (error) { } catch (error) {
console.error("Failed to save login credentials:", error); logger.error("Failed to save login credentials:", error);
} }
} }
@@ -364,7 +367,7 @@ export class LoginCredentialsManager {
try { try {
await AsyncStorage.removeItem(STORAGE_KEYS.LOGIN_CREDENTIALS); await AsyncStorage.removeItem(STORAGE_KEYS.LOGIN_CREDENTIALS);
} catch (error) { } catch (error) {
console.error("Failed to clear login credentials:", error); logger.error("Failed to clear login credentials:", error);
} }
} }
} }

View File

@@ -1,5 +1,8 @@
import TcpSocket from 'react-native-tcp-socket'; import TcpSocket from 'react-native-tcp-socket';
import NetInfo from '@react-native-community/netinfo'; import NetInfo from '@react-native-community/netinfo';
import Logger from '@/utils/Logger';
const logger = Logger.withTag('TCPHttpServer');
const PORT = 12346; const PORT = 12346;
@@ -59,7 +62,7 @@ class TCPHttpServer {
return { method, url, headers, body }; return { method, url, headers, body };
} catch (error) { } catch (error) {
console.info('[TCPHttpServer] Error parsing HTTP request:', error); logger.info('[TCPHttpServer] Error parsing HTTP request:', error);
return null; return null;
} }
} }
@@ -108,14 +111,14 @@ class TCPHttpServer {
} }
if (this.isRunning) { if (this.isRunning) {
console.log('[TCPHttpServer] Server is already running.'); logger.debug('[TCPHttpServer] Server is already running.');
return `http://${ipAddress}:${PORT}`; return `http://${ipAddress}:${PORT}`;
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
this.server = TcpSocket.createServer((socket: TcpSocket.Socket) => { this.server = TcpSocket.createServer((socket: TcpSocket.Socket) => {
console.log('[TCPHttpServer] Client connected'); logger.debug('[TCPHttpServer] Client connected');
let requestData = ''; let requestData = '';
@@ -140,7 +143,7 @@ class TCPHttpServer {
socket.write(errorResponse); socket.write(errorResponse);
} }
} catch (error) { } catch (error) {
console.info('[TCPHttpServer] Error handling request:', error); logger.info('[TCPHttpServer] Error handling request:', error);
const errorResponse = this.formatHttpResponse({ const errorResponse = this.formatHttpResponse({
statusCode: 500, statusCode: 500,
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
@@ -155,28 +158,28 @@ class TCPHttpServer {
}); });
socket.on('error', (error: Error) => { socket.on('error', (error: Error) => {
console.info('[TCPHttpServer] Socket error:', error); logger.info('[TCPHttpServer] Socket error:', error);
}); });
socket.on('close', () => { socket.on('close', () => {
console.log('[TCPHttpServer] Client disconnected'); logger.debug('[TCPHttpServer] Client disconnected');
}); });
}); });
this.server.listen({ port: PORT, host: '0.0.0.0' }, () => { this.server.listen({ port: PORT, host: '0.0.0.0' }, () => {
console.log(`[TCPHttpServer] Server listening on ${ipAddress}:${PORT}`); logger.debug(`[TCPHttpServer] Server listening on ${ipAddress}:${PORT}`);
this.isRunning = true; this.isRunning = true;
resolve(`http://${ipAddress}:${PORT}`); resolve(`http://${ipAddress}:${PORT}`);
}); });
this.server.on('error', (error: Error) => { this.server.on('error', (error: Error) => {
console.info('[TCPHttpServer] Server error:', error); logger.info('[TCPHttpServer] Server error:', error);
this.isRunning = false; this.isRunning = false;
reject(error); reject(error);
}); });
} catch (error) { } catch (error) {
console.info('[TCPHttpServer] Failed to start server:', error); logger.info('[TCPHttpServer] Failed to start server:', error);
reject(error); reject(error);
} }
}); });
@@ -187,7 +190,7 @@ class TCPHttpServer {
this.server.close(); this.server.close();
this.server = null; this.server = null;
this.isRunning = false; this.isRunning = false;
console.log('[TCPHttpServer] Server stopped'); logger.debug('[TCPHttpServer] Server stopped');
} }
} }

View File

@@ -3,6 +3,9 @@ import FileViewer from "react-native-file-viewer";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import { version as currentVersion } from "../package.json"; import { version as currentVersion } from "../package.json";
import { UPDATE_CONFIG } from "../constants/UpdateConfig"; import { UPDATE_CONFIG } from "../constants/UpdateConfig";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('UpdateService');
interface VersionInfo { interface VersionInfo {
version: string; version: string;
@@ -47,7 +50,7 @@ class UpdateService {
}; };
} catch (error) { } catch (error) {
retries++; retries++;
console.info(`Error checking version (attempt ${retries}/${maxRetries}):`, error); logger.info(`Error checking version (attempt ${retries}/${maxRetries}):`, error);
if (retries === maxRetries) { if (retries === maxRetries) {
Toast.show({ type: "error", text1: "检查更新失败", text2: "无法获取版本信息,请检查网络连接" }); Toast.show({ type: "error", text1: "检查更新失败", text2: "无法获取版本信息,请检查网络连接" });
@@ -86,14 +89,14 @@ class UpdateService {
for (const file of filesToDelete) { for (const file of filesToDelete) {
try { try {
await ReactNativeBlobUtil.fs.unlink(`${dirs.DocumentDir}/${file}`); await ReactNativeBlobUtil.fs.unlink(`${dirs.DocumentDir}/${file}`);
console.log(`Cleaned old APK file: ${file}`); logger.debug(`Cleaned old APK file: ${file}`);
} catch (deleteError) { } catch (deleteError) {
console.warn(`Failed to delete old APK file ${file}:`, deleteError); logger.warn(`Failed to delete old APK file ${file}:`, deleteError);
} }
} }
} }
} catch (error) { } catch (error) {
console.warn('Failed to clean old APK files:', error); logger.warn('Failed to clean old APK files:', error);
} }
} }
@@ -138,11 +141,11 @@ class UpdateService {
} }
const res = await task; const res = await task;
console.log(`APK downloaded successfully: ${filePath}`); logger.debug(`APK downloaded successfully: ${filePath}`);
return res.path(); return res.path();
} catch (error) { } catch (error) {
retries++; retries++;
console.info(`Error downloading APK (attempt ${retries}/${maxRetries}):`, error); logger.info(`Error downloading APK (attempt ${retries}/${maxRetries}):`, error);
if (retries === maxRetries) { if (retries === maxRetries) {
Toast.show({ type: "error", text1: "下载失败", text2: "APK下载失败请检查网络连接" }); Toast.show({ type: "error", text1: "下载失败", text2: "APK下载失败请检查网络连接" });
@@ -173,7 +176,7 @@ class UpdateService {
displayName: "OrionTV Update", displayName: "OrionTV Update",
}); });
} catch (error) { } catch (error) {
console.info("Error installing APK:", error); logger.info("Error installing APK:", error);
// 提供更详细的错误信息 // 提供更详细的错误信息
if (error instanceof Error) { if (error instanceof Error) {

View File

@@ -3,6 +3,9 @@ import Cookies from "@react-native-cookies/cookies";
import { api } from "@/services/api"; import { api } from "@/services/api";
import { useSettingsStore } from "./settingsStore"; import { useSettingsStore } from "./settingsStore";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import Logger from "@/utils/Logger";
const logger = Logger.withTag('AuthStore');
interface AuthState { interface AuthState {
isLoggedIn: boolean; isLoggedIn: boolean;
@@ -69,7 +72,7 @@ const useAuthStore = create<AuthState>((set) => ({
} }
} }
} catch (error) { } catch (error) {
console.info("Failed to check login status:", error); logger.error("Failed to check login status:", error);
if (error instanceof Error && error.message === "UNAUTHORIZED") { if (error instanceof Error && error.message === "UNAUTHORIZED") {
set({ isLoggedIn: false, isLoginModalVisible: true }); set({ isLoggedIn: false, isLoginModalVisible: true });
} else { } else {
@@ -82,7 +85,7 @@ const useAuthStore = create<AuthState>((set) => ({
await Cookies.clearAll(); await Cookies.clearAll();
set({ isLoggedIn: false, isLoginModalVisible: true }); set({ isLoggedIn: false, isLoginModalVisible: true });
} catch (error) { } catch (error) {
console.info("Failed to logout:", error); logger.error("Failed to logout:", error);
} }
}, },
})); }));

View File

@@ -3,6 +3,9 @@ import { SearchResult, api } from "@/services/api";
import { getResolutionFromM3U8 } from "@/services/m3u8"; import { getResolutionFromM3U8 } from "@/services/m3u8";
import { useSettingsStore } from "@/stores/settingsStore"; import { useSettingsStore } from "@/stores/settingsStore";
import { FavoriteManager } from "@/services/storage"; import { FavoriteManager } from "@/services/storage";
import Logger from "@/utils/Logger";
const logger = Logger.withTag('DetailStore');
export type SearchResultWithResolution = SearchResult & { resolution?: string | null }; export type SearchResultWithResolution = SearchResult & { resolution?: string | null };
@@ -40,7 +43,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
init: async (q, preferredSource, id) => { init: async (q, preferredSource, id) => {
const perfStart = performance.now(); const perfStart = performance.now();
console.info(`[PERF] DetailStore.init START - q: ${q}, preferredSource: ${preferredSource}, id: ${id}`); logger.info(`[PERF] DetailStore.init START - q: ${q}, preferredSource: ${preferredSource}, id: ${id}`);
const { controller: oldController } = get(); const { controller: oldController } = get();
if (oldController) { if (oldController) {
@@ -63,7 +66,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
const processAndSetResults = async (results: SearchResult[], merge = false) => { const processAndSetResults = async (results: SearchResult[], merge = false) => {
const resolutionStart = performance.now(); const resolutionStart = performance.now();
console.info(`[PERF] Resolution detection START - processing ${results.length} sources`); logger.info(`[PERF] Resolution detection START - processing ${results.length} sources`);
const resultsWithResolution = await Promise.all( const resultsWithResolution = await Promise.all(
results.map(async (searchResult) => { results.map(async (searchResult) => {
@@ -75,17 +78,17 @@ const useDetailStore = create<DetailState>((set, get) => ({
} }
} catch (e) { } catch (e) {
if ((e as Error).name !== "AbortError") { if ((e as Error).name !== "AbortError") {
console.info(`Failed to get resolution for ${searchResult.source_name}`, e); logger.info(`Failed to get resolution for ${searchResult.source_name}`, e);
} }
} }
const m3u8End = performance.now(); const m3u8End = performance.now();
console.info(`[PERF] M3U8 resolution for ${searchResult.source_name}: ${(m3u8End - m3u8Start).toFixed(2)}ms (${resolution || 'failed'})`); logger.info(`[PERF] M3U8 resolution for ${searchResult.source_name}: ${(m3u8End - m3u8Start).toFixed(2)}ms (${resolution || 'failed'})`);
return { ...searchResult, resolution }; return { ...searchResult, resolution };
}) })
); );
const resolutionEnd = performance.now(); const resolutionEnd = performance.now();
console.info(`[PERF] Resolution detection COMPLETE - took ${(resolutionEnd - resolutionStart).toFixed(2)}ms`); logger.info(`[PERF] Resolution detection COMPLETE - took ${(resolutionEnd - resolutionStart).toFixed(2)}ms`);
if (signal.aborted) return; if (signal.aborted) return;
@@ -110,7 +113,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
// Optimization for favorite navigation // Optimization for favorite navigation
if (preferredSource && id) { if (preferredSource && id) {
const searchPreferredStart = performance.now(); const searchPreferredStart = performance.now();
console.info(`[PERF] API searchVideo (preferred) START - source: ${preferredSource}, query: "${q}"`); logger.info(`[PERF] API searchVideo (preferred) START - source: ${preferredSource}, query: "${q}"`);
let preferredResult: SearchResult[] = []; let preferredResult: SearchResult[] = [];
let preferredSearchError: any = null; let preferredSearchError: any = null;
@@ -120,52 +123,52 @@ const useDetailStore = create<DetailState>((set, get) => ({
preferredResult = response.results; preferredResult = response.results;
} catch (error) { } catch (error) {
preferredSearchError = error; preferredSearchError = error;
console.error(`[ERROR] API searchVideo (preferred) FAILED - source: ${preferredSource}, error:`, error); logger.error(`[ERROR] API searchVideo (preferred) FAILED - source: ${preferredSource}, error:`, error);
} }
const searchPreferredEnd = performance.now(); const searchPreferredEnd = performance.now();
console.info(`[PERF] API searchVideo (preferred) END - took ${(searchPreferredEnd - searchPreferredStart).toFixed(2)}ms, results: ${preferredResult.length}, error: ${!!preferredSearchError}`); logger.info(`[PERF] API searchVideo (preferred) END - took ${(searchPreferredEnd - searchPreferredStart).toFixed(2)}ms, results: ${preferredResult.length}, error: ${!!preferredSearchError}`);
if (signal.aborted) return; if (signal.aborted) return;
// 检查preferred source结果 // 检查preferred source结果
if (preferredResult.length > 0) { if (preferredResult.length > 0) {
console.info(`[SUCCESS] Preferred source "${preferredSource}" found ${preferredResult.length} results for "${q}"`); logger.info(`[SUCCESS] Preferred source "${preferredSource}" found ${preferredResult.length} results for "${q}"`);
await processAndSetResults(preferredResult, false); await processAndSetResults(preferredResult, false);
set({ loading: false }); set({ loading: false });
} else { } else {
// 降级策略preferred source失败时立即尝试所有源 // 降级策略preferred source失败时立即尝试所有源
if (preferredSearchError) { if (preferredSearchError) {
console.warn(`[FALLBACK] Preferred source "${preferredSource}" failed with error, trying all sources immediately`); logger.warn(`[FALLBACK] Preferred source "${preferredSource}" failed with error, trying all sources immediately`);
} else { } else {
console.warn(`[FALLBACK] Preferred source "${preferredSource}" returned 0 results for "${q}", trying all sources immediately`); logger.warn(`[FALLBACK] Preferred source "${preferredSource}" returned 0 results for "${q}", trying all sources immediately`);
} }
// 立即尝试所有源,不再依赖后台搜索 // 立即尝试所有源,不再依赖后台搜索
const fallbackStart = performance.now(); const fallbackStart = performance.now();
console.info(`[PERF] FALLBACK search (all sources) START - query: "${q}"`); logger.info(`[PERF] FALLBACK search (all sources) START - query: "${q}"`);
try { try {
const { results: allResults } = await api.searchVideos(q); const { results: allResults } = await api.searchVideos(q);
const fallbackEnd = performance.now(); const fallbackEnd = performance.now();
console.info(`[PERF] FALLBACK search END - took ${(fallbackEnd - fallbackStart).toFixed(2)}ms, total results: ${allResults.length}`); logger.info(`[PERF] FALLBACK search END - took ${(fallbackEnd - fallbackStart).toFixed(2)}ms, total results: ${allResults.length}`);
const filteredResults = allResults.filter(item => item.title === q); const filteredResults = allResults.filter(item => item.title === q);
console.info(`[FALLBACK] Filtered results: ${filteredResults.length} matches for "${q}"`); logger.info(`[FALLBACK] Filtered results: ${filteredResults.length} matches for "${q}"`);
if (filteredResults.length > 0) { if (filteredResults.length > 0) {
console.info(`[SUCCESS] FALLBACK search found results, proceeding with ${filteredResults[0].source_name}`); logger.info(`[SUCCESS] FALLBACK search found results, proceeding with ${filteredResults[0].source_name}`);
await processAndSetResults(filteredResults, false); await processAndSetResults(filteredResults, false);
set({ loading: false }); set({ loading: false });
} else { } else {
console.error(`[ERROR] FALLBACK search found no matching results for "${q}"`); logger.error(`[ERROR] FALLBACK search found no matching results for "${q}"`);
set({ set({
error: `未找到 "${q}" 的播放源,请检查标题或稍后重试`, error: `未找到 "${q}" 的播放源,请检查标题或稍后重试`,
loading: false loading: false
}); });
} }
} catch (fallbackError) { } catch (fallbackError) {
console.error(`[ERROR] FALLBACK search FAILED:`, fallbackError); logger.error(`[ERROR] FALLBACK search FAILED:`, fallbackError);
set({ set({
error: `搜索失败:${fallbackError instanceof Error ? fallbackError.message : '网络错误,请稍后重试'}`, error: `搜索失败:${fallbackError instanceof Error ? fallbackError.message : '网络错误,请稍后重试'}`,
loading: false loading: false
@@ -176,39 +179,39 @@ const useDetailStore = create<DetailState>((set, get) => ({
// 后台搜索如果preferred source成功的话 // 后台搜索如果preferred source成功的话
if (preferredResult.length > 0) { if (preferredResult.length > 0) {
const searchAllStart = performance.now(); const searchAllStart = performance.now();
console.info(`[PERF] API searchVideos (background) START`); logger.info(`[PERF] API searchVideos (background) START`);
try { try {
const { results: allResults } = await api.searchVideos(q); const { results: allResults } = await api.searchVideos(q);
const searchAllEnd = performance.now(); const searchAllEnd = performance.now();
console.info(`[PERF] API searchVideos (background) END - took ${(searchAllEnd - searchAllStart).toFixed(2)}ms, results: ${allResults.length}`); logger.info(`[PERF] API searchVideos (background) END - took ${(searchAllEnd - searchAllStart).toFixed(2)}ms, results: ${allResults.length}`);
if (signal.aborted) return; if (signal.aborted) return;
await processAndSetResults(allResults.filter(item => item.title === q), true); await processAndSetResults(allResults.filter(item => item.title === q), true);
} catch (backgroundError) { } catch (backgroundError) {
console.warn(`[WARN] Background search failed, but preferred source already succeeded:`, backgroundError); logger.warn(`[WARN] Background search failed, but preferred source already succeeded:`, backgroundError);
} }
} }
} else { } else {
// Standard navigation: fetch resources, then fetch details one by one // Standard navigation: fetch resources, then fetch details one by one
const resourcesStart = performance.now(); const resourcesStart = performance.now();
console.info(`[PERF] API getResources START - query: "${q}"`); logger.info(`[PERF] API getResources START - query: "${q}"`);
try { try {
const allResources = await api.getResources(signal); const allResources = await api.getResources(signal);
const resourcesEnd = performance.now(); const resourcesEnd = performance.now();
console.info(`[PERF] API getResources END - took ${(resourcesEnd - resourcesStart).toFixed(2)}ms, resources: ${allResources.length}`); logger.info(`[PERF] API getResources END - took ${(resourcesEnd - resourcesStart).toFixed(2)}ms, resources: ${allResources.length}`);
const enabledResources = videoSource.enabledAll const enabledResources = videoSource.enabledAll
? allResources ? allResources
: allResources.filter((r) => videoSource.sources[r.key]); : allResources.filter((r) => videoSource.sources[r.key]);
console.info(`[PERF] Enabled resources: ${enabledResources.length}/${allResources.length}`); logger.info(`[PERF] Enabled resources: ${enabledResources.length}/${allResources.length}`);
if (enabledResources.length === 0) { if (enabledResources.length === 0) {
console.error(`[ERROR] No enabled resources available for search`); logger.error(`[ERROR] No enabled resources available for search`);
set({ set({
error: "没有可用的视频源,请检查设置或联系管理员", error: "没有可用的视频源,请检查设置或联系管理员",
loading: false loading: false
@@ -223,22 +226,22 @@ const useDetailStore = create<DetailState>((set, get) => ({
const searchStart = performance.now(); const searchStart = performance.now();
const { results } = await api.searchVideo(q, resource.key, signal); const { results } = await api.searchVideo(q, resource.key, signal);
const searchEnd = performance.now(); const searchEnd = performance.now();
console.info(`[PERF] API searchVideo (${resource.name}) took ${(searchEnd - searchStart).toFixed(2)}ms, results: ${results.length}`); logger.info(`[PERF] API searchVideo (${resource.name}) took ${(searchEnd - searchStart).toFixed(2)}ms, results: ${results.length}`);
if (results.length > 0) { if (results.length > 0) {
totalResults += results.length; totalResults += results.length;
console.info(`[SUCCESS] Source "${resource.name}" found ${results.length} results for "${q}"`); logger.info(`[SUCCESS] Source "${resource.name}" found ${results.length} results for "${q}"`);
await processAndSetResults(results, true); await processAndSetResults(results, true);
if (!firstResultFound) { if (!firstResultFound) {
set({ loading: false }); // Stop loading indicator on first result set({ loading: false }); // Stop loading indicator on first result
firstResultFound = true; firstResultFound = true;
console.info(`[SUCCESS] First result found from "${resource.name}", stopping loading indicator`); logger.info(`[SUCCESS] First result found from "${resource.name}", stopping loading indicator`);
} }
} else { } else {
console.warn(`[WARN] Source "${resource.name}" returned 0 results for "${q}"`); logger.warn(`[WARN] Source "${resource.name}" returned 0 results for "${q}"`);
} }
} catch (error) { } catch (error) {
console.error(`[ERROR] Failed to fetch from ${resource.name}:`, error); logger.error(`[ERROR] Failed to fetch from ${resource.name}:`, error);
} }
}); });
@@ -246,16 +249,16 @@ const useDetailStore = create<DetailState>((set, get) => ({
// 检查是否找到任何结果 // 检查是否找到任何结果
if (totalResults === 0) { if (totalResults === 0) {
console.error(`[ERROR] All sources returned 0 results for "${q}"`); logger.error(`[ERROR] All sources returned 0 results for "${q}"`);
set({ set({
error: `未找到 "${q}" 的播放源,请尝试其他关键词或稍后重试`, error: `未找到 "${q}" 的播放源,请尝试其他关键词或稍后重试`,
loading: false loading: false
}); });
} else { } else {
console.info(`[SUCCESS] Standard search completed, total results: ${totalResults}`); logger.info(`[SUCCESS] Standard search completed, total results: ${totalResults}`);
} }
} catch (resourceError) { } catch (resourceError) {
console.error(`[ERROR] Failed to get resources:`, resourceError); logger.error(`[ERROR] Failed to get resources:`, resourceError);
set({ set({
error: `获取视频源失败:${resourceError instanceof Error ? resourceError.message : '网络错误,请稍后重试'}`, error: `获取视频源失败:${resourceError instanceof Error ? resourceError.message : '网络错误,请稍后重试'}`,
loading: false loading: false
@@ -269,45 +272,45 @@ const useDetailStore = create<DetailState>((set, get) => ({
// 最终检查:如果所有搜索都完成但仍然没有结果 // 最终检查:如果所有搜索都完成但仍然没有结果
if (finalState.searchResults.length === 0 && !finalState.error) { if (finalState.searchResults.length === 0 && !finalState.error) {
console.error(`[ERROR] All search attempts completed but no results found for "${q}"`); logger.error(`[ERROR] All search attempts completed but no results found for "${q}"`);
set({ error: `未找到 "${q}" 的播放源,请检查标题拼写或稍后重试` }); set({ error: `未找到 "${q}" 的播放源,请检查标题拼写或稍后重试` });
} else if (finalState.searchResults.length > 0) { } else if (finalState.searchResults.length > 0) {
console.info(`[SUCCESS] DetailStore.init completed successfully with ${finalState.searchResults.length} sources`); logger.info(`[SUCCESS] DetailStore.init completed successfully with ${finalState.searchResults.length} sources`);
} }
if (finalState.detail) { if (finalState.detail) {
const { source, id } = finalState.detail; const { source, id } = finalState.detail;
console.info(`[INFO] Checking favorite status for source: ${source}, id: ${id}`); logger.info(`[INFO] Checking favorite status for source: ${source}, id: ${id}`);
try { try {
const isFavorited = await FavoriteManager.isFavorited(source, id.toString()); const isFavorited = await FavoriteManager.isFavorited(source, id.toString());
set({ isFavorited }); set({ isFavorited });
console.info(`[INFO] Favorite status: ${isFavorited}`); logger.info(`[INFO] Favorite status: ${isFavorited}`);
} catch (favoriteError) { } catch (favoriteError) {
console.warn(`[WARN] Failed to check favorite status:`, favoriteError); logger.warn(`[WARN] Failed to check favorite status:`, favoriteError);
} }
} else { } else {
console.warn(`[WARN] No detail found after all search attempts for "${q}"`); logger.warn(`[WARN] No detail found after all search attempts for "${q}"`);
} }
const favoriteCheckEnd = performance.now(); const favoriteCheckEnd = performance.now();
console.info(`[PERF] Favorite check took ${(favoriteCheckEnd - favoriteCheckStart).toFixed(2)}ms`); logger.info(`[PERF] Favorite check took ${(favoriteCheckEnd - favoriteCheckStart).toFixed(2)}ms`);
} catch (e) { } catch (e) {
if ((e as Error).name !== "AbortError") { if ((e as Error).name !== "AbortError") {
console.error(`[ERROR] DetailStore.init caught unexpected error:`, e); logger.error(`[ERROR] DetailStore.init caught unexpected error:`, e);
const errorMessage = e instanceof Error ? e.message : "获取数据失败"; const errorMessage = e instanceof Error ? e.message : "获取数据失败";
set({ error: `搜索失败:${errorMessage}` }); set({ error: `搜索失败:${errorMessage}` });
} else { } else {
console.info(`[INFO] DetailStore.init aborted by user`); logger.info(`[INFO] DetailStore.init aborted by user`);
} }
} finally { } finally {
if (!signal.aborted) { if (!signal.aborted) {
set({ loading: false, allSourcesLoaded: true }); set({ loading: false, allSourcesLoaded: true });
console.info(`[INFO] DetailStore.init cleanup completed`); logger.info(`[INFO] DetailStore.init cleanup completed`);
} }
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] DetailStore.init COMPLETE - total time: ${(perfEnd - perfStart).toFixed(2)}ms`); logger.info(`[PERF] DetailStore.init COMPLETE - total time: ${(perfEnd - perfStart).toFixed(2)}ms`);
} }
}, },
@@ -346,8 +349,8 @@ const useDetailStore = create<DetailState>((set, get) => ({
const newFailedSources = new Set(failedSources); const newFailedSources = new Set(failedSources);
newFailedSources.add(source); newFailedSources.add(source);
console.warn(`[SOURCE_FAILED] Marking source "${source}" as failed due to: ${reason}`); logger.warn(`[SOURCE_FAILED] Marking source "${source}" as failed due to: ${reason}`);
console.info(`[SOURCE_FAILED] Total failed sources: ${newFailedSources.size}`); logger.info(`[SOURCE_FAILED] Total failed sources: ${newFailedSources.size}`);
set({ failedSources: newFailedSources }); set({ failedSources: newFailedSources });
}, },
@@ -355,8 +358,8 @@ const useDetailStore = create<DetailState>((set, get) => ({
getNextAvailableSource: (currentSource: string, episodeIndex: number) => { getNextAvailableSource: (currentSource: string, episodeIndex: number) => {
const { searchResults, failedSources } = get(); const { searchResults, failedSources } = get();
console.info(`[SOURCE_SELECTION] Looking for alternative to "${currentSource}" for episode ${episodeIndex + 1}`); logger.info(`[SOURCE_SELECTION] Looking for alternative to "${currentSource}" for episode ${episodeIndex + 1}`);
console.info(`[SOURCE_SELECTION] Failed sources: [${Array.from(failedSources).join(', ')}]`); logger.info(`[SOURCE_SELECTION] Failed sources: [${Array.from(failedSources).join(', ')}]`);
// 过滤掉当前source和已失败的sources // 过滤掉当前source和已失败的sources
const availableSources = searchResults.filter(result => const availableSources = searchResults.filter(result =>
@@ -366,13 +369,13 @@ const useDetailStore = create<DetailState>((set, get) => ({
result.episodes.length > episodeIndex result.episodes.length > episodeIndex
); );
console.info(`[SOURCE_SELECTION] Available sources: ${availableSources.length}`); logger.info(`[SOURCE_SELECTION] Available sources: ${availableSources.length}`);
availableSources.forEach(source => { availableSources.forEach(source => {
console.info(`[SOURCE_SELECTION] - ${source.source} (${source.source_name}): ${source.episodes?.length || 0} episodes`); logger.info(`[SOURCE_SELECTION] - ${source.source} (${source.source_name}): ${source.episodes?.length || 0} episodes`);
}); });
if (availableSources.length === 0) { if (availableSources.length === 0) {
console.error(`[SOURCE_SELECTION] No available sources for episode ${episodeIndex + 1}`); logger.error(`[SOURCE_SELECTION] No available sources for episode ${episodeIndex + 1}`);
return null; return null;
} }
@@ -394,7 +397,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
}); });
const selectedSource = sortedSources[0]; const selectedSource = sortedSources[0];
console.info(`[SOURCE_SELECTION] Selected fallback source: ${selectedSource.source} (${selectedSource.source_name}) with resolution: ${selectedSource.resolution || 'unknown'}`); logger.info(`[SOURCE_SELECTION] Selected fallback source: ${selectedSource.source} (${selectedSource.source_name}) with resolution: ${selectedSource.resolution || 'unknown'}`);
return selectedSource; return selectedSource;
}, },

View File

@@ -4,6 +4,9 @@ import { AVPlaybackStatus, Video } from "expo-av";
import { RefObject } from "react"; import { RefObject } from "react";
import { PlayRecord, PlayRecordManager, PlayerSettingsManager } from "@/services/storage"; import { PlayRecord, PlayRecordManager, PlayerSettingsManager } from "@/services/storage";
import useDetailStore, { episodesSelectorBySource } from "./detailStore"; import useDetailStore, { episodesSelectorBySource } from "./detailStore";
import Logger from '@/utils/Logger';
const logger = Logger.withTag('PlayerStore');
interface Episode { interface Episode {
url: string; url: string;
@@ -82,17 +85,17 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
loadVideo: async ({ source, id, episodeIndex, position, title }) => { loadVideo: async ({ source, id, episodeIndex, position, title }) => {
const perfStart = performance.now(); const perfStart = performance.now();
console.info(`[PERF] PlayerStore.loadVideo START - source: ${source}, id: ${id}, title: ${title}`); logger.info(`[PERF] PlayerStore.loadVideo START - source: ${source}, id: ${id}, title: ${title}`);
let detail = useDetailStore.getState().detail; let detail = useDetailStore.getState().detail;
let episodes: string[] = []; let episodes: string[] = [];
// 如果有detail使用detail的source获取episodes否则使用传入的source // 如果有detail使用detail的source获取episodes否则使用传入的source
if (detail && detail.source) { if (detail && detail.source) {
console.info(`[INFO] Using existing detail source "${detail.source}" to get episodes`); logger.info(`[INFO] Using existing detail source "${detail.source}" to get episodes`);
episodes = episodesSelectorBySource(detail.source)(useDetailStore.getState()); episodes = episodesSelectorBySource(detail.source)(useDetailStore.getState());
} else { } else {
console.info(`[INFO] No existing detail, using provided source "${source}" to get episodes`); logger.info(`[INFO] No existing detail, using provided source "${source}" to get episodes`);
episodes = episodesSelectorBySource(source)(useDetailStore.getState()); episodes = episodesSelectorBySource(source)(useDetailStore.getState());
} }
@@ -101,73 +104,73 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
}); });
const needsDetailInit = !detail || !episodes || episodes.length === 0 || detail.title !== title; const needsDetailInit = !detail || !episodes || episodes.length === 0 || detail.title !== title;
console.info(`[PERF] Detail check - needsInit: ${needsDetailInit}, hasDetail: ${!!detail}, episodesCount: ${episodes?.length || 0}`); logger.info(`[PERF] Detail check - needsInit: ${needsDetailInit}, hasDetail: ${!!detail}, episodesCount: ${episodes?.length || 0}`);
if (needsDetailInit) { if (needsDetailInit) {
const detailInitStart = performance.now(); const detailInitStart = performance.now();
console.info(`[PERF] DetailStore.init START - ${title}`); logger.info(`[PERF] DetailStore.init START - ${title}`);
await useDetailStore.getState().init(title, source, id); await useDetailStore.getState().init(title, source, id);
const detailInitEnd = performance.now(); const detailInitEnd = performance.now();
console.info(`[PERF] DetailStore.init END - took ${(detailInitEnd - detailInitStart).toFixed(2)}ms`); logger.info(`[PERF] DetailStore.init END - took ${(detailInitEnd - detailInitStart).toFixed(2)}ms`);
detail = useDetailStore.getState().detail; detail = useDetailStore.getState().detail;
if (!detail) { if (!detail) {
console.error(`[ERROR] Detail not found after initialization for "${title}" (source: ${source}, id: ${id})`); logger.error(`[ERROR] Detail not found after initialization for "${title}" (source: ${source}, id: ${id})`);
// 检查DetailStore的错误状态 // 检查DetailStore的错误状态
const detailStoreState = useDetailStore.getState(); const detailStoreState = useDetailStore.getState();
if (detailStoreState.error) { if (detailStoreState.error) {
console.error(`[ERROR] DetailStore error: ${detailStoreState.error}`); logger.error(`[ERROR] DetailStore error: ${detailStoreState.error}`);
set({ set({
isLoading: false, isLoading: false,
// 可以选择在这里设置一个错误状态但playerStore可能没有error字段 // 可以选择在这里设置一个错误状态但playerStore可能没有error字段
}); });
} else { } else {
console.error(`[ERROR] DetailStore init completed but no detail found and no error reported`); logger.error(`[ERROR] DetailStore init completed but no detail found and no error reported`);
set({ isLoading: false }); set({ isLoading: false });
} }
return; return;
} }
// 使用DetailStore找到的实际source来获取episodes而不是原始的preferredSource // 使用DetailStore找到的实际source来获取episodes而不是原始的preferredSource
console.info(`[INFO] Using actual source "${detail.source}" instead of preferred source "${source}"`); logger.info(`[INFO] Using actual source "${detail.source}" instead of preferred source "${source}"`);
episodes = episodesSelectorBySource(detail.source)(useDetailStore.getState()); episodes = episodesSelectorBySource(detail.source)(useDetailStore.getState());
if (!episodes || episodes.length === 0) { if (!episodes || episodes.length === 0) {
console.error(`[ERROR] No episodes found for "${title}" from source "${detail.source}" (${detail.source_name})`); logger.error(`[ERROR] No episodes found for "${title}" from source "${detail.source}" (${detail.source_name})`);
// 尝试从searchResults中直接获取episodes // 尝试从searchResults中直接获取episodes
const detailStoreState = useDetailStore.getState(); const detailStoreState = useDetailStore.getState();
console.info(`[INFO] Available sources in searchResults: ${detailStoreState.searchResults.map(r => `${r.source}(${r.episodes?.length || 0} episodes)`).join(', ')}`); logger.info(`[INFO] Available sources in searchResults: ${detailStoreState.searchResults.map(r => `${r.source}(${r.episodes?.length || 0} episodes)`).join(', ')}`);
// 如果当前source没有episodes尝试使用第一个有episodes的source // 如果当前source没有episodes尝试使用第一个有episodes的source
const sourceWithEpisodes = detailStoreState.searchResults.find(r => r.episodes && r.episodes.length > 0); const sourceWithEpisodes = detailStoreState.searchResults.find(r => r.episodes && r.episodes.length > 0);
if (sourceWithEpisodes) { if (sourceWithEpisodes) {
console.info(`[FALLBACK] Using alternative source "${sourceWithEpisodes.source}" with ${sourceWithEpisodes.episodes.length} episodes`); logger.info(`[FALLBACK] Using alternative source "${sourceWithEpisodes.source}" with ${sourceWithEpisodes.episodes.length} episodes`);
episodes = sourceWithEpisodes.episodes; episodes = sourceWithEpisodes.episodes;
// 更新detail为有episodes的source // 更新detail为有episodes的source
detail = sourceWithEpisodes; detail = sourceWithEpisodes;
} else { } else {
console.error(`[ERROR] No source with episodes found in searchResults`); logger.error(`[ERROR] No source with episodes found in searchResults`);
set({ isLoading: false }); set({ isLoading: false });
return; return;
} }
} }
console.info(`[SUCCESS] Detail and episodes loaded - source: ${detail.source_name}, episodes: ${episodes.length}`); logger.info(`[SUCCESS] Detail and episodes loaded - source: ${detail.source_name}, episodes: ${episodes.length}`);
} else { } else {
console.info(`[PERF] Skipping DetailStore.init - using cached data`); logger.info(`[PERF] Skipping DetailStore.init - using cached data`);
// 即使是缓存的数据也要确保使用正确的source获取episodes // 即使是缓存的数据也要确保使用正确的source获取episodes
if (detail && detail.source && detail.source !== source) { if (detail && detail.source && detail.source !== source) {
console.info(`[INFO] Cached detail source "${detail.source}" differs from provided source "${source}", updating episodes`); logger.info(`[INFO] Cached detail source "${detail.source}" differs from provided source "${source}", updating episodes`);
episodes = episodesSelectorBySource(detail.source)(useDetailStore.getState()); episodes = episodesSelectorBySource(detail.source)(useDetailStore.getState());
if (!episodes || episodes.length === 0) { if (!episodes || episodes.length === 0) {
console.warn(`[WARN] Cached detail source "${detail.source}" has no episodes, trying provided source "${source}"`); logger.warn(`[WARN] Cached detail source "${detail.source}" has no episodes, trying provided source "${source}"`);
episodes = episodesSelectorBySource(source)(useDetailStore.getState()); episodes = episodesSelectorBySource(source)(useDetailStore.getState());
} }
} }
@@ -175,31 +178,31 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
// 最终验证确保我们有有效的detail和episodes数据 // 最终验证确保我们有有效的detail和episodes数据
if (!detail) { if (!detail) {
console.error(`[ERROR] Final check failed: detail is null`); logger.error(`[ERROR] Final check failed: detail is null`);
set({ isLoading: false }); set({ isLoading: false });
return; return;
} }
if (!episodes || episodes.length === 0) { if (!episodes || episodes.length === 0) {
console.error(`[ERROR] Final check failed: no episodes available for source "${detail.source}" (${detail.source_name})`); logger.error(`[ERROR] Final check failed: no episodes available for source "${detail.source}" (${detail.source_name})`);
set({ isLoading: false }); set({ isLoading: false });
return; return;
} }
console.info(`[SUCCESS] Final validation passed - detail: ${detail.source_name}, episodes: ${episodes.length}`); logger.info(`[SUCCESS] Final validation passed - detail: ${detail.source_name}, episodes: ${episodes.length}`);
try { try {
const storageStart = performance.now(); const storageStart = performance.now();
console.info(`[PERF] Storage operations START`); logger.info(`[PERF] Storage operations START`);
const playRecord = await PlayRecordManager.get(detail!.source, detail!.id.toString()); const playRecord = await PlayRecordManager.get(detail!.source, detail!.id.toString());
const storagePlayRecordEnd = performance.now(); const storagePlayRecordEnd = performance.now();
console.info(`[PERF] PlayRecordManager.get took ${(storagePlayRecordEnd - storageStart).toFixed(2)}ms`); logger.info(`[PERF] PlayRecordManager.get took ${(storagePlayRecordEnd - storageStart).toFixed(2)}ms`);
const playerSettings = await PlayerSettingsManager.get(detail!.source, detail!.id.toString()); const playerSettings = await PlayerSettingsManager.get(detail!.source, detail!.id.toString());
const storageEnd = performance.now(); const storageEnd = performance.now();
console.info(`[PERF] PlayerSettingsManager.get took ${(storageEnd - storagePlayRecordEnd).toFixed(2)}ms`); logger.info(`[PERF] PlayerSettingsManager.get took ${(storageEnd - storagePlayRecordEnd).toFixed(2)}ms`);
console.info(`[PERF] Total storage operations took ${(storageEnd - storageStart).toFixed(2)}ms`); logger.info(`[PERF] Total storage operations took ${(storageEnd - storageStart).toFixed(2)}ms`);
const initialPositionFromRecord = playRecord?.play_time ? playRecord.play_time * 1000 : 0; const initialPositionFromRecord = playRecord?.play_time ? playRecord.play_time * 1000 : 0;
const savedPlaybackRate = playerSettings?.playbackRate || 1.0; const savedPlaybackRate = playerSettings?.playbackRate || 1.0;
@@ -210,7 +213,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
title: `${index + 1}`, title: `${index + 1}`,
})); }));
const episodesMappingEnd = performance.now(); const episodesMappingEnd = performance.now();
console.info(`[PERF] Episodes mapping (${episodes.length} episodes) took ${(episodesMappingEnd - episodesMappingStart).toFixed(2)}ms`); logger.info(`[PERF] Episodes mapping (${episodes.length} episodes) took ${(episodesMappingEnd - episodesMappingStart).toFixed(2)}ms`);
set({ set({
isLoading: false, isLoading: false,
@@ -223,14 +226,14 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
}); });
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] PlayerStore.loadVideo COMPLETE - total time: ${(perfEnd - perfStart).toFixed(2)}ms`); logger.info(`[PERF] PlayerStore.loadVideo COMPLETE - total time: ${(perfEnd - perfStart).toFixed(2)}ms`);
} catch (error) { } catch (error) {
console.info("Failed to load play record", error); logger.debug("Failed to load play record", error);
set({ isLoading: false }); set({ isLoading: false });
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[PERF] PlayerStore.loadVideo ERROR - total time: ${(perfEnd - perfStart).toFixed(2)}ms`); logger.info(`[PERF] PlayerStore.loadVideo ERROR - total time: ${(perfEnd - perfStart).toFixed(2)}ms`);
} }
}, },
@@ -247,7 +250,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
try { try {
await videoRef?.current?.replayAsync(); await videoRef?.current?.replayAsync();
} catch (error) { } catch (error) {
console.info("Failed to replay video:", error); logger.debug("Failed to replay video:", error);
Toast.show({ type: "error", text1: "播放失败" }); Toast.show({ type: "error", text1: "播放失败" });
} }
} }
@@ -263,7 +266,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
await videoRef?.current?.playAsync(); await videoRef?.current?.playAsync();
} }
} catch (error) { } catch (error) {
console.info("Failed to toggle play/pause:", error); logger.debug("Failed to toggle play/pause:", error);
Toast.show({ type: "error", text1: "操作失败" }); Toast.show({ type: "error", text1: "操作失败" });
} }
} }
@@ -277,7 +280,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
try { try {
await videoRef?.current?.setPositionAsync(newPosition); await videoRef?.current?.setPositionAsync(newPosition);
} catch (error) { } catch (error) {
console.info("Failed to seek video:", error); logger.debug("Failed to seek video:", error);
Toast.show({ type: "error", text1: "快进/快退失败" }); Toast.show({ type: "error", text1: "快进/快退失败" });
} }
@@ -383,7 +386,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
handlePlaybackStatusUpdate: (newStatus) => { handlePlaybackStatusUpdate: (newStatus) => {
if (!newStatus.isLoaded) { if (!newStatus.isLoaded) {
if (newStatus.error) { if (newStatus.error) {
console.info(`Playback Error: ${newStatus.error}`); logger.debug(`Playback Error: ${newStatus.error}`);
} }
set({ status: newStatus }); set({ status: newStatus });
return; return;
@@ -444,7 +447,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
await PlayerSettingsManager.save(detail.source, detail.id.toString(), { playbackRate: rate }); await PlayerSettingsManager.save(detail.source, detail.id.toString(), { playbackRate: rate });
} }
} catch (error) { } catch (error) {
console.info("Failed to set playback rate:", error); logger.debug("Failed to set playback rate:", error);
} }
}, },
@@ -468,14 +471,14 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
handleVideoError: async (errorType: 'ssl' | 'network' | 'other', failedUrl: string) => { handleVideoError: async (errorType: 'ssl' | 'network' | 'other', failedUrl: string) => {
const perfStart = performance.now(); const perfStart = performance.now();
console.error(`[VIDEO_ERROR] Handling ${errorType} error for URL: ${failedUrl}`); logger.error(`[VIDEO_ERROR] Handling ${errorType} error for URL: ${failedUrl}`);
const detailStoreState = useDetailStore.getState(); const detailStoreState = useDetailStore.getState();
const { detail } = detailStoreState; const { detail } = detailStoreState;
const { currentEpisodeIndex } = get(); const { currentEpisodeIndex } = get();
if (!detail) { if (!detail) {
console.error(`[VIDEO_ERROR] Cannot fallback - no detail available`); logger.error(`[VIDEO_ERROR] Cannot fallback - no detail available`);
set({ isLoading: false }); set({ isLoading: false });
return; return;
} }
@@ -489,7 +492,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
const fallbackSource = useDetailStore.getState().getNextAvailableSource(currentSource, currentEpisodeIndex); const fallbackSource = useDetailStore.getState().getNextAvailableSource(currentSource, currentEpisodeIndex);
if (!fallbackSource) { if (!fallbackSource) {
console.error(`[VIDEO_ERROR] No fallback sources available for episode ${currentEpisodeIndex + 1}`); logger.error(`[VIDEO_ERROR] No fallback sources available for episode ${currentEpisodeIndex + 1}`);
Toast.show({ Toast.show({
type: "error", type: "error",
text1: "播放失败", text1: "播放失败",
@@ -499,7 +502,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
return; return;
} }
console.info(`[VIDEO_ERROR] Switching to fallback source: ${fallbackSource.source} (${fallbackSource.source_name})`); logger.info(`[VIDEO_ERROR] Switching to fallback source: ${fallbackSource.source} (${fallbackSource.source_name})`);
try { try {
// 更新DetailStore的当前detail为fallback source // 更新DetailStore的当前detail为fallback source
@@ -519,8 +522,8 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
}); });
const perfEnd = performance.now(); const perfEnd = performance.now();
console.info(`[VIDEO_ERROR] Successfully switched to fallback source in ${(perfEnd - perfStart).toFixed(2)}ms`); logger.info(`[VIDEO_ERROR] Successfully switched to fallback source in ${(perfEnd - perfStart).toFixed(2)}ms`);
console.info(`[VIDEO_ERROR] New episode URL: ${newEpisodes[currentEpisodeIndex].substring(0, 100)}...`); logger.info(`[VIDEO_ERROR] New episode URL: ${newEpisodes[currentEpisodeIndex].substring(0, 100)}...`);
Toast.show({ Toast.show({
type: "success", type: "success",
@@ -528,11 +531,11 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
text2: `正在使用 ${fallbackSource.source_name}` text2: `正在使用 ${fallbackSource.source_name}`
}); });
} else { } else {
console.error(`[VIDEO_ERROR] Fallback source doesn't have episode ${currentEpisodeIndex + 1}`); logger.error(`[VIDEO_ERROR] Fallback source doesn't have episode ${currentEpisodeIndex + 1}`);
set({ isLoading: false }); set({ isLoading: false });
} }
} catch (error) { } catch (error) {
console.error(`[VIDEO_ERROR] Failed to switch to fallback source:`, error); logger.error(`[VIDEO_ERROR] Failed to switch to fallback source:`, error);
set({ isLoading: false }); set({ isLoading: false });
} }
}, },
@@ -556,13 +559,13 @@ export const selectCurrentEpisode = (state: PlayerState) => {
} else { } else {
// 仅在调试模式下打印 // 仅在调试模式下打印
if (__DEV__) { if (__DEV__) {
console.info(`[PERF] selectCurrentEpisode - episode found but invalid URL: ${episode?.url}`); logger.debug(`[PERF] selectCurrentEpisode - episode found but invalid URL: ${episode?.url}`);
} }
} }
} else { } else {
// 仅在调试模式下打印 // 仅在调试模式下打印
if (__DEV__) { if (__DEV__) {
console.info(`[PERF] selectCurrentEpisode - no valid episode: episodes.length=${state.episodes?.length}, currentIndex=${state.currentEpisodeIndex}`); logger.debug(`[PERF] selectCurrentEpisode - no valid episode: episodes.length=${state.episodes?.length}, currentIndex=${state.currentEpisodeIndex}`);
} }
} }
return undefined; return undefined;

View File

@@ -1,5 +1,8 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { remoteControlService } from '@/services/remoteControlService'; import { remoteControlService } from '@/services/remoteControlService';
import Logger from '@/utils/Logger';
const logger = Logger.withTag('RemoteControlStore');
interface RemoteControlState { interface RemoteControlState {
isServerRunning: boolean; isServerRunning: boolean;
@@ -30,23 +33,23 @@ export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
} }
remoteControlService.init({ remoteControlService.init({
onMessage: (message: string) => { onMessage: (message: string) => {
console.log('[RemoteControlStore] Received message:', message); logger.debug('Received message:', message);
const currentState = get(); const currentState = get();
// Use the current targetPage from the store // Use the current targetPage from the store
set({ lastMessage: message, targetPage: currentState.targetPage }); set({ lastMessage: message, targetPage: currentState.targetPage });
}, },
onHandshake: () => { onHandshake: () => {
console.log('[RemoteControlStore] Handshake successful'); logger.debug('Handshake successful');
set({ isModalVisible: false }) set({ isModalVisible: false })
}, },
}); });
try { try {
const url = await remoteControlService.startServer(); const url = await remoteControlService.startServer();
console.log(`[RemoteControlStore] Server started, URL: ${url}`); logger.info('Server started, URL:', url);
set({ isServerRunning: true, serverUrl: url, error: null }); set({ isServerRunning: true, serverUrl: url, error: null });
} catch { } catch {
const errorMessage = '启动失败,请强制退应用后重试。'; const errorMessage = '启动失败,请强制退应用后重试。';
console.info('[RemoteControlStore] Failed to start server:', errorMessage); logger.error('Failed to start server:', errorMessage);
set({ error: errorMessage }); set({ error: errorMessage });
} }
}, },

View File

@@ -2,6 +2,9 @@ import { create } from "zustand";
import { SettingsManager } from "@/services/storage"; import { SettingsManager } from "@/services/storage";
import { api, ServerConfig } from "@/services/api"; import { api, ServerConfig } from "@/services/api";
import { storageConfig } from "@/services/storageConfig"; import { storageConfig } from "@/services/storageConfig";
import Logger from "@/utils/Logger";
const logger = Logger.withTag('SettingsStore');
interface SettingsState { interface SettingsState {
apiBaseUrl: string; apiBaseUrl: string;
@@ -65,7 +68,7 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
} }
} catch (error) { } catch (error) {
set({ serverConfig: null }); set({ serverConfig: null });
console.info("Failed to fetch server config:", error); logger.error("Failed to fetch server config:", error);
} finally { } finally {
set({ isLoadingServerConfig: false }); set({ isLoadingServerConfig: false });
} }

View File

@@ -2,6 +2,9 @@ import { create } from 'zustand';
import updateService from '../services/updateService'; import updateService from '../services/updateService';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import Toast from 'react-native-toast-message'; import Toast from 'react-native-toast-message';
import Logger from '@/utils/Logger';
const logger = Logger.withTag('UpdateStore');
interface UpdateState { interface UpdateState {
// 状态 // 状态
@@ -151,7 +154,7 @@ export const useUpdateStore = create<UpdateState>((set, get) => ({
// 安装开始后,关闭弹窗 // 安装开始后,关闭弹窗
set({ showUpdateModal: false }); set({ showUpdateModal: false });
} catch (error) { } catch (error) {
console.info('安装失败:', error); logger.error('安装失败:', error);
set({ set({
error: error instanceof Error ? error.message : '安装失败', error: error instanceof Error ? error.message : '安装失败',
}); });
@@ -200,6 +203,6 @@ export const initUpdateStore = async () => {
skipVersion: skipVersion || null, skipVersion: skipVersion || null,
}); });
} catch (error) { } catch (error) {
console.info('初始化更新存储失败:', error); logger.error('初始化更新存储失败:', error);
} }
}; };

149
utils/Logger.ts Normal file
View File

@@ -0,0 +1,149 @@
/**
* 统一日志管理器
* 在开发环境输出完整日志,生产环境移除所有日志代码
*/
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
}
interface LoggerOptions {
tag?: string;
level?: LogLevel;
}
class LoggerClass {
private minLevel: LogLevel = LogLevel.DEBUG;
/**
* 设置最小日志级别
*/
setMinLevel(level: LogLevel): void {
if (__DEV__) {
this.minLevel = level;
}
}
/**
* 格式化日志输出
*/
private formatMessage(level: string, tag: string | undefined, message: any, ...args: any[]): void {
if (!__DEV__) return;
const timestamp = new Date().toISOString().substr(11, 12);
const prefix = tag ? `[${timestamp}][${level}][${tag}]` : `[${timestamp}][${level}]`;
switch (level) {
case 'DEBUG':
console.log(prefix, message, ...args);
break;
case 'INFO':
console.info(prefix, message, ...args);
break;
case 'WARN':
console.warn(prefix, message, ...args);
break;
case 'ERROR':
console.error(prefix, message, ...args);
break;
}
}
/**
* 调试级别日志
*/
debug(message: any, ...args: any[]): void;
debug(options: LoggerOptions, message: any, ...args: any[]): void;
debug(optionsOrMessage: LoggerOptions | any, message?: any, ...args: any[]): void {
if (!__DEV__) return;
if (this.minLevel > LogLevel.DEBUG) return;
if (typeof optionsOrMessage === 'object' && optionsOrMessage.tag !== undefined) {
const options = optionsOrMessage as LoggerOptions;
this.formatMessage('DEBUG', options.tag, message, ...args);
} else {
this.formatMessage('DEBUG', undefined, optionsOrMessage, message, ...args);
}
}
/**
* 信息级别日志
*/
info(message: any, ...args: any[]): void;
info(options: LoggerOptions, message: any, ...args: any[]): void;
info(optionsOrMessage: LoggerOptions | any, message?: any, ...args: any[]): void {
if (!__DEV__) return;
if (this.minLevel > LogLevel.INFO) return;
if (typeof optionsOrMessage === 'object' && optionsOrMessage.tag !== undefined) {
const options = optionsOrMessage as LoggerOptions;
this.formatMessage('INFO', options.tag, message, ...args);
} else {
this.formatMessage('INFO', undefined, optionsOrMessage, message, ...args);
}
}
/**
* 警告级别日志
*/
warn(message: any, ...args: any[]): void;
warn(options: LoggerOptions, message: any, ...args: any[]): void;
warn(optionsOrMessage: LoggerOptions | any, message?: any, ...args: any[]): void {
if (!__DEV__) return;
if (this.minLevel > LogLevel.WARN) return;
if (typeof optionsOrMessage === 'object' && optionsOrMessage.tag !== undefined) {
const options = optionsOrMessage as LoggerOptions;
this.formatMessage('WARN', options.tag, message, ...args);
} else {
this.formatMessage('WARN', undefined, optionsOrMessage, message, ...args);
}
}
/**
* 错误级别日志
*/
error(message: any, ...args: any[]): void;
error(options: LoggerOptions, message: any, ...args: any[]): void;
error(optionsOrMessage: LoggerOptions | any, message?: any, ...args: any[]): void {
if (!__DEV__) return;
if (this.minLevel > LogLevel.ERROR) return;
if (typeof optionsOrMessage === 'object' && optionsOrMessage.tag !== undefined) {
const options = optionsOrMessage as LoggerOptions;
this.formatMessage('ERROR', options.tag, message, ...args);
} else {
this.formatMessage('ERROR', undefined, optionsOrMessage, message, ...args);
}
}
/**
* 创建带标签的日志实例
*/
withTag(tag: string): LoggerClass {
const taggedLogger = new LoggerClass();
taggedLogger.minLevel = this.minLevel;
const originalDebug = taggedLogger.debug.bind(taggedLogger);
const originalInfo = taggedLogger.info.bind(taggedLogger);
const originalWarn = taggedLogger.warn.bind(taggedLogger);
const originalError = taggedLogger.error.bind(taggedLogger);
taggedLogger.debug = (message: any, ...args: any[]) => originalDebug({ tag }, message, ...args);
taggedLogger.info = (message: any, ...args: any[]) => originalInfo({ tag }, message, ...args);
taggedLogger.warn = (message: any, ...args: any[]) => originalWarn({ tag }, message, ...args);
taggedLogger.error = (message: any, ...args: any[]) => originalError({ tag }, message, ...args);
return taggedLogger;
}
}
export const Logger = new LoggerClass();
export default Logger;

View File

@@ -3064,6 +3064,11 @@ babel-plugin-transform-flow-enums@^0.0.2:
dependencies: dependencies:
"@babel/plugin-syntax-flow" "^7.12.1" "@babel/plugin-syntax-flow" "^7.12.1"
babel-plugin-transform-remove-console@^6.9.4:
version "6.9.4"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz#b980360c067384e24b357a588d807d3c83527780"
integrity sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==
babel-preset-current-node-syntax@^1.0.0: babel-preset-current-node-syntax@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30"
@@ -8827,16 +8832,7 @@ string-length@^5.0.1:
char-regex "^2.0.0" char-regex "^2.0.0"
strip-ansi "^7.0.1" strip-ansi "^7.0.1"
"string-width-cjs@npm:string-width@^4.2.0": "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3" version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -8927,7 +8923,7 @@ string_decoder@~1.1.1:
dependencies: dependencies:
safe-buffer "~5.1.0" safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -8941,13 +8937,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
dependencies: dependencies:
ansi-regex "^4.1.0" ansi-regex "^4.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1: strip-ansi@^7.0.1:
version "7.1.0" version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -9779,7 +9768,7 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -9797,15 +9786,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0: wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"