mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
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:
@@ -15,6 +15,9 @@ import { useUpdateStore, initUpdateStore } from "@/stores/updateStore";
|
||||
import { UpdateModal } from "@/components/UpdateModal";
|
||||
import { UPDATE_CONFIG } from "@/constants/UpdateConfig";
|
||||
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.
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
@@ -48,7 +51,7 @@ export default function RootLayout() {
|
||||
if (loaded || error) {
|
||||
SplashScreen.hideAsync();
|
||||
if (error) {
|
||||
console.warn(`Error in loading fonts: ${error}`);
|
||||
logger.warn(`Error in loading fonts: ${error}`);
|
||||
}
|
||||
}
|
||||
}, [loaded, error]);
|
||||
|
||||
15
app/play.tsx
15
app/play.tsx
@@ -17,11 +17,14 @@ import Toast from "react-native-toast-message";
|
||||
import usePlayerStore, { selectCurrentEpisode } from "@/stores/playerStore";
|
||||
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
||||
import { useVideoHandlers } from "@/hooks/useVideoHandlers";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('PlayScreen');
|
||||
|
||||
// 优化的加载动画组件
|
||||
const LoadingContainer = memo(
|
||||
({ 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: ${
|
||||
currentEpisode?.url ? "exists" : "missing"
|
||||
}`
|
||||
@@ -130,21 +133,21 @@ export default function PlayScreen() {
|
||||
|
||||
useEffect(() => {
|
||||
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);
|
||||
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 });
|
||||
} 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();
|
||||
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 () => {
|
||||
console.info(`[PERF] PlayScreen unmounting - calling reset()`);
|
||||
logger.info(`[PERF] PlayScreen unmounting - calling reset()`);
|
||||
reset(); // Reset state when component unmounts
|
||||
};
|
||||
}, [episodeIndex, source, position, setVideoRef, reset, loadVideo, id, title]);
|
||||
|
||||
@@ -18,6 +18,9 @@ import { getCommonResponsiveStyles } from "@/utils/ResponsiveStyles";
|
||||
import ResponsiveNavigation from "@/components/navigation/ResponsiveNavigation";
|
||||
import ResponsiveHeader from "@/components/navigation/ResponsiveHeader";
|
||||
import { DeviceUtils } from "@/utils/DeviceUtils";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('SearchScreen');
|
||||
|
||||
export default function SearchScreen() {
|
||||
const [keyword, setKeyword] = useState("");
|
||||
@@ -37,7 +40,7 @@ export default function SearchScreen() {
|
||||
|
||||
useEffect(() => {
|
||||
if (lastMessage && targetPage === 'search') {
|
||||
console.log("Received remote input:", lastMessage);
|
||||
logger.debug("Received remote input:", lastMessage);
|
||||
const realMessage = lastMessage.split("_")[0];
|
||||
setKeyword(realMessage);
|
||||
handleSearch(realMessage);
|
||||
@@ -72,7 +75,7 @@ export default function SearchScreen() {
|
||||
}
|
||||
} catch (err) {
|
||||
setError("搜索失败,请稍后重试。");
|
||||
console.info("Search failed:", err);
|
||||
logger.info("Search failed:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
|
||||
const plugins = [];
|
||||
|
||||
// 在生产环境移除console调用以优化性能
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
plugins.push('transform-remove-console');
|
||||
}
|
||||
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: [],
|
||||
plugins,
|
||||
};
|
||||
};
|
||||
@@ -8,6 +8,9 @@ import { ThemedText } from "@/components/ThemedText";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
||||
import { DeviceUtils } from "@/utils/DeviceUtils";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('ResponsiveVideoCard');
|
||||
|
||||
interface VideoCardProps extends React.ComponentProps<typeof TouchableOpacity> {
|
||||
id: string;
|
||||
@@ -138,7 +141,7 @@ const ResponsiveVideoCard = forwardRef<View, VideoCardProps>(
|
||||
router.replace("/");
|
||||
}
|
||||
} catch (error) {
|
||||
console.info("Failed to delete play record:", error);
|
||||
logger.info("Failed to delete play record:", error);
|
||||
Alert.alert("错误", "删除观看记录失败,请重试");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,13 +3,16 @@ import { View, Text, StyleSheet, Modal, FlatList } from "react-native";
|
||||
import { StyledButton } from "./StyledButton";
|
||||
import useDetailStore from "@/stores/detailStore";
|
||||
import usePlayerStore from "@/stores/playerStore";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('SourceSelectionModal');
|
||||
|
||||
export const SourceSelectionModal: React.FC = () => {
|
||||
const { showSourceModal, setShowSourceModal, loadVideo, currentEpisodeIndex, status } = usePlayerStore();
|
||||
const { searchResults, detail, setDetail } = useDetailStore();
|
||||
|
||||
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) {
|
||||
const newDetail = searchResults[index];
|
||||
setDetail(newDetail);
|
||||
|
||||
@@ -8,6 +8,9 @@ import { ThemedText } from "@/components/ThemedText";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
||||
import { DeviceUtils } from "@/utils/DeviceUtils";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('VideoCardMobile');
|
||||
|
||||
interface VideoCardMobileProps extends React.ComponentProps<typeof TouchableOpacity> {
|
||||
id: string;
|
||||
@@ -97,7 +100,7 @@ const VideoCardMobile = forwardRef<View, VideoCardMobileProps>(
|
||||
await PlayRecordManager.remove(source, id);
|
||||
onRecordDeleted?.();
|
||||
} catch (error) {
|
||||
console.info("Failed to delete play record:", error);
|
||||
logger.info("Failed to delete play record:", error);
|
||||
Alert.alert("错误", "删除观看记录失败,请重试");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,6 +8,9 @@ import { ThemedText } from "@/components/ThemedText";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import { useResponsiveLayout } from "@/hooks/useResponsiveLayout";
|
||||
import { DeviceUtils } from "@/utils/DeviceUtils";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('VideoCardTablet');
|
||||
|
||||
interface VideoCardTabletProps extends React.ComponentProps<typeof TouchableOpacity> {
|
||||
id: string;
|
||||
@@ -119,7 +122,7 @@ const VideoCardTablet = forwardRef<View, VideoCardTabletProps>(
|
||||
await PlayRecordManager.remove(source, id);
|
||||
onRecordDeleted?.();
|
||||
} catch (error) {
|
||||
console.info("Failed to delete play record:", error);
|
||||
logger.info("Failed to delete play record:", error);
|
||||
Alert.alert("错误", "删除观看记录失败,请重试");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,6 +6,9 @@ import { PlayRecordManager } from "@/services/storage";
|
||||
import { API } from "@/services/api";
|
||||
import { ThemedText } from "@/components/ThemedText";
|
||||
import { Colors } from "@/constants/Colors";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('VideoCardTV');
|
||||
|
||||
interface VideoCardProps extends React.ComponentProps<typeof TouchableOpacity> {
|
||||
id: string;
|
||||
@@ -131,7 +134,7 @@ const VideoCard = forwardRef<View, VideoCardProps>(
|
||||
router.replace("/");
|
||||
}
|
||||
} catch (error) {
|
||||
console.info("Failed to delete play record:", error);
|
||||
logger.info("Failed to delete play record:", error);
|
||||
Alert.alert("错误", "删除观看记录失败,请重试");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"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",
|
||||
"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",
|
||||
"test": "jest --watchAll",
|
||||
"test-ci": "jest --ci --coverage --no-cache",
|
||||
@@ -67,6 +67,7 @@
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "~18.2.45",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-expo": "~7.1.2",
|
||||
"jest": "^29.2.1",
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('API');
|
||||
|
||||
// region: --- Interface Definitions ---
|
||||
export interface DoubanItem {
|
||||
title: string;
|
||||
@@ -215,13 +219,13 @@ export class API {
|
||||
|
||||
// 添加安全检查
|
||||
if (!config || !config.Config.SourceConfig) {
|
||||
console.warn('API response missing SourceConfig:', config);
|
||||
logger.warn('API response missing SourceConfig:', config);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 确保 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 [];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('M3U');
|
||||
|
||||
export interface Channel {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -59,7 +63,7 @@ export const fetchAndParseM3u = async (m3uUrl: string): Promise<Channel[]> => {
|
||||
const m3uText = await response.text();
|
||||
return parseM3U(m3uText);
|
||||
} catch (error) {
|
||||
console.info("Error fetching or parsing M3U:", error);
|
||||
logger.info("Error fetching or parsing M3U:", error);
|
||||
return []; // Return empty array on error
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('M3U8');
|
||||
|
||||
interface CacheEntry {
|
||||
resolution: string | null;
|
||||
timestamp: number;
|
||||
@@ -11,18 +15,18 @@ export const getResolutionFromM3U8 = async (
|
||||
signal?: AbortSignal
|
||||
): Promise<string | null> => {
|
||||
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
|
||||
const cachedEntry = resolutionCache[url];
|
||||
if (cachedEntry && Date.now() - cachedEntry.timestamp < CACHE_DURATION) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -30,7 +34,7 @@ export const getResolutionFromM3U8 = async (
|
||||
const fetchStart = performance.now();
|
||||
const response = await fetch(url, { signal });
|
||||
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) {
|
||||
return null;
|
||||
@@ -56,7 +60,7 @@ export const getResolutionFromM3U8 = async (
|
||||
}
|
||||
|
||||
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
|
||||
resolutionCache[url] = {
|
||||
@@ -65,12 +69,12 @@ export const getResolutionFromM3U8 = async (
|
||||
};
|
||||
|
||||
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;
|
||||
} catch (error) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import TCPHttpServer from "./tcpHttpServer";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('RemoteControl');
|
||||
|
||||
const getRemotePageHTML = () => {
|
||||
return `
|
||||
@@ -25,7 +28,7 @@ const getRemotePageHTML = () => {
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
fetch('/handshake', { method: 'POST' }).catch(console.info);
|
||||
fetch('/handshake', { method: 'POST' }).catch(err => logger.info('Handshake failed:', err));
|
||||
});
|
||||
function send() {
|
||||
const input = document.getElementById("text");
|
||||
@@ -36,7 +39,7 @@ const getRemotePageHTML = () => {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: value })
|
||||
})
|
||||
.catch(err => console.info(err));
|
||||
.catch(err => logger.info('Message send failed:', err));
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
@@ -58,7 +61,7 @@ class RemoteControlService {
|
||||
|
||||
private setupRequestHandler() {
|
||||
this.httpServer.setRequestHandler((request) => {
|
||||
console.log("[RemoteControl] Received request:", request.method, request.url);
|
||||
logger.debug("[RemoteControl] Received request:", request.method, request.url);
|
||||
|
||||
try {
|
||||
if (request.method === "GET" && request.url === "/") {
|
||||
@@ -80,7 +83,7 @@ class RemoteControlService {
|
||||
body: JSON.stringify({ status: "ok" }),
|
||||
};
|
||||
} catch (parseError) {
|
||||
console.info("[RemoteControl] Failed to parse message body:", parseError);
|
||||
logger.info("[RemoteControl] Failed to parse message body:", parseError);
|
||||
return {
|
||||
statusCode: 400,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -102,7 +105,7 @@ class RemoteControlService {
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.info("[RemoteControl] Request handler error:", error);
|
||||
logger.info("[RemoteControl] Request handler error:", error);
|
||||
return {
|
||||
statusCode: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -118,20 +121,20 @@ class RemoteControlService {
|
||||
}
|
||||
|
||||
public async startServer(): Promise<string> {
|
||||
console.log("[RemoteControl] Attempting to start server...");
|
||||
logger.debug("[RemoteControl] Attempting to start server...");
|
||||
|
||||
try {
|
||||
const url = await this.httpServer.start();
|
||||
console.log(`[RemoteControl] Server started successfully at: ${url}`);
|
||||
logger.debug(`[RemoteControl] Server started successfully at: ${url}`);
|
||||
return url;
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
|
||||
public stopServer() {
|
||||
console.log("[RemoteControl] Stopping server...");
|
||||
logger.debug("[RemoteControl] Stopping server...");
|
||||
this.httpServer.stop();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { api, PlayRecord as ApiPlayRecord, Favorite as ApiFavorite } from "./api";
|
||||
import { storageConfig } from "./storageConfig";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('Storage');
|
||||
|
||||
// --- Storage Keys ---
|
||||
const STORAGE_KEYS = {
|
||||
@@ -53,20 +56,20 @@ export class PlayerSettingsManager {
|
||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.PLAYER_SETTINGS);
|
||||
return data ? JSON.parse(data) : {};
|
||||
} catch (error) {
|
||||
console.info("Failed to get all player settings:", error);
|
||||
logger.info("Failed to get all player settings:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
static async get(source: string, id: string): Promise<PlayerSettings | null> {
|
||||
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 result = allSettings[generateKey(source, id)] || null;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -107,7 +110,7 @@ export class FavoriteManager {
|
||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.FAVORITES);
|
||||
return data ? JSON.parse(data) : {};
|
||||
} catch (error) {
|
||||
console.info("Failed to get all local favorites:", error);
|
||||
logger.info("Failed to get all local favorites:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -175,7 +178,7 @@ export class PlayRecordManager {
|
||||
static async getAll(): Promise<Record<string, PlayRecord>> {
|
||||
const perfStart = performance.now();
|
||||
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> = {};
|
||||
if (storageType === "localstorage") {
|
||||
@@ -183,17 +186,17 @@ export class PlayRecordManager {
|
||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.PLAY_RECORDS);
|
||||
apiRecords = data ? JSON.parse(data) : {};
|
||||
} catch (error) {
|
||||
console.info("Failed to get all local play records:", error);
|
||||
logger.info("Failed to get all local play records:", error);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
const apiStart = performance.now();
|
||||
console.info(`[PERF] API getPlayRecords START`);
|
||||
logger.info(`[PERF] API getPlayRecords START`);
|
||||
|
||||
apiRecords = await api.getPlayRecords();
|
||||
|
||||
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();
|
||||
@@ -206,7 +209,7 @@ export class PlayRecordManager {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -232,13 +235,13 @@ export class PlayRecordManager {
|
||||
const perfStart = performance.now();
|
||||
const key = generateKey(source, id);
|
||||
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 result = records[key] || null;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -279,7 +282,7 @@ export class SearchHistoryManager {
|
||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.SEARCH_HISTORY);
|
||||
return data ? JSON.parse(data) : [];
|
||||
} catch (error) {
|
||||
console.info("Failed to get local search history:", error);
|
||||
logger.info("Failed to get local search history:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -324,7 +327,7 @@ export class SettingsManager {
|
||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.SETTINGS);
|
||||
return data ? { ...defaultSettings, ...JSON.parse(data) } : defaultSettings;
|
||||
} catch (error) {
|
||||
console.info("Failed to get settings:", error);
|
||||
logger.info("Failed to get settings:", error);
|
||||
return defaultSettings;
|
||||
}
|
||||
}
|
||||
@@ -347,7 +350,7 @@ export class LoginCredentialsManager {
|
||||
const data = await AsyncStorage.getItem(STORAGE_KEYS.LOGIN_CREDENTIALS);
|
||||
return data ? JSON.parse(data) : null;
|
||||
} catch (error) {
|
||||
console.info("Failed to get login credentials:", error);
|
||||
logger.info("Failed to get login credentials:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -356,7 +359,7 @@ export class LoginCredentialsManager {
|
||||
try {
|
||||
await AsyncStorage.setItem(STORAGE_KEYS.LOGIN_CREDENTIALS, JSON.stringify(credentials));
|
||||
} 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 {
|
||||
await AsyncStorage.removeItem(STORAGE_KEYS.LOGIN_CREDENTIALS);
|
||||
} catch (error) {
|
||||
console.error("Failed to clear login credentials:", error);
|
||||
logger.error("Failed to clear login credentials:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import TcpSocket from 'react-native-tcp-socket';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('TCPHttpServer');
|
||||
|
||||
const PORT = 12346;
|
||||
|
||||
@@ -59,7 +62,7 @@ class TCPHttpServer {
|
||||
|
||||
return { method, url, headers, body };
|
||||
} catch (error) {
|
||||
console.info('[TCPHttpServer] Error parsing HTTP request:', error);
|
||||
logger.info('[TCPHttpServer] Error parsing HTTP request:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -108,14 +111,14 @@ class TCPHttpServer {
|
||||
}
|
||||
|
||||
if (this.isRunning) {
|
||||
console.log('[TCPHttpServer] Server is already running.');
|
||||
logger.debug('[TCPHttpServer] Server is already running.');
|
||||
return `http://${ipAddress}:${PORT}`;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.server = TcpSocket.createServer((socket: TcpSocket.Socket) => {
|
||||
console.log('[TCPHttpServer] Client connected');
|
||||
logger.debug('[TCPHttpServer] Client connected');
|
||||
|
||||
let requestData = '';
|
||||
|
||||
@@ -140,7 +143,7 @@ class TCPHttpServer {
|
||||
socket.write(errorResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
console.info('[TCPHttpServer] Error handling request:', error);
|
||||
logger.info('[TCPHttpServer] Error handling request:', error);
|
||||
const errorResponse = this.formatHttpResponse({
|
||||
statusCode: 500,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
@@ -155,28 +158,28 @@ class TCPHttpServer {
|
||||
});
|
||||
|
||||
socket.on('error', (error: Error) => {
|
||||
console.info('[TCPHttpServer] Socket error:', error);
|
||||
logger.info('[TCPHttpServer] Socket error:', error);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
console.log('[TCPHttpServer] Client disconnected');
|
||||
logger.debug('[TCPHttpServer] Client disconnected');
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
resolve(`http://${ipAddress}:${PORT}`);
|
||||
});
|
||||
|
||||
this.server.on('error', (error: Error) => {
|
||||
console.info('[TCPHttpServer] Server error:', error);
|
||||
logger.info('[TCPHttpServer] Server error:', error);
|
||||
this.isRunning = false;
|
||||
reject(error);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.info('[TCPHttpServer] Failed to start server:', error);
|
||||
logger.info('[TCPHttpServer] Failed to start server:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
@@ -187,7 +190,7 @@ class TCPHttpServer {
|
||||
this.server.close();
|
||||
this.server = null;
|
||||
this.isRunning = false;
|
||||
console.log('[TCPHttpServer] Server stopped');
|
||||
logger.debug('[TCPHttpServer] Server stopped');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ import FileViewer from "react-native-file-viewer";
|
||||
import Toast from "react-native-toast-message";
|
||||
import { version as currentVersion } from "../package.json";
|
||||
import { UPDATE_CONFIG } from "../constants/UpdateConfig";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('UpdateService');
|
||||
|
||||
interface VersionInfo {
|
||||
version: string;
|
||||
@@ -47,7 +50,7 @@ class UpdateService {
|
||||
};
|
||||
} catch (error) {
|
||||
retries++;
|
||||
console.info(`Error checking version (attempt ${retries}/${maxRetries}):`, error);
|
||||
logger.info(`Error checking version (attempt ${retries}/${maxRetries}):`, error);
|
||||
|
||||
if (retries === maxRetries) {
|
||||
Toast.show({ type: "error", text1: "检查更新失败", text2: "无法获取版本信息,请检查网络连接" });
|
||||
@@ -86,14 +89,14 @@ class UpdateService {
|
||||
for (const file of filesToDelete) {
|
||||
try {
|
||||
await ReactNativeBlobUtil.fs.unlink(`${dirs.DocumentDir}/${file}`);
|
||||
console.log(`Cleaned old APK file: ${file}`);
|
||||
logger.debug(`Cleaned old APK file: ${file}`);
|
||||
} catch (deleteError) {
|
||||
console.warn(`Failed to delete old APK file ${file}:`, deleteError);
|
||||
logger.warn(`Failed to delete old APK file ${file}:`, deleteError);
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
console.log(`APK downloaded successfully: ${filePath}`);
|
||||
logger.debug(`APK downloaded successfully: ${filePath}`);
|
||||
return res.path();
|
||||
} catch (error) {
|
||||
retries++;
|
||||
console.info(`Error downloading APK (attempt ${retries}/${maxRetries}):`, error);
|
||||
logger.info(`Error downloading APK (attempt ${retries}/${maxRetries}):`, error);
|
||||
|
||||
if (retries === maxRetries) {
|
||||
Toast.show({ type: "error", text1: "下载失败", text2: "APK下载失败,请检查网络连接" });
|
||||
@@ -173,7 +176,7 @@ class UpdateService {
|
||||
displayName: "OrionTV Update",
|
||||
});
|
||||
} catch (error) {
|
||||
console.info("Error installing APK:", error);
|
||||
logger.info("Error installing APK:", error);
|
||||
|
||||
// 提供更详细的错误信息
|
||||
if (error instanceof Error) {
|
||||
|
||||
@@ -3,6 +3,9 @@ import Cookies from "@react-native-cookies/cookies";
|
||||
import { api } from "@/services/api";
|
||||
import { useSettingsStore } from "./settingsStore";
|
||||
import Toast from "react-native-toast-message";
|
||||
import Logger from "@/utils/Logger";
|
||||
|
||||
const logger = Logger.withTag('AuthStore');
|
||||
|
||||
interface AuthState {
|
||||
isLoggedIn: boolean;
|
||||
@@ -69,7 +72,7 @@ const useAuthStore = create<AuthState>((set) => ({
|
||||
}
|
||||
}
|
||||
} 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") {
|
||||
set({ isLoggedIn: false, isLoginModalVisible: true });
|
||||
} else {
|
||||
@@ -82,7 +85,7 @@ const useAuthStore = create<AuthState>((set) => ({
|
||||
await Cookies.clearAll();
|
||||
set({ isLoggedIn: false, isLoginModalVisible: true });
|
||||
} catch (error) {
|
||||
console.info("Failed to logout:", error);
|
||||
logger.error("Failed to logout:", error);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -3,6 +3,9 @@ import { SearchResult, api } from "@/services/api";
|
||||
import { getResolutionFromM3U8 } from "@/services/m3u8";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { FavoriteManager } from "@/services/storage";
|
||||
import Logger from "@/utils/Logger";
|
||||
|
||||
const logger = Logger.withTag('DetailStore');
|
||||
|
||||
export type SearchResultWithResolution = SearchResult & { resolution?: string | null };
|
||||
|
||||
@@ -40,7 +43,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
|
||||
init: async (q, preferredSource, id) => {
|
||||
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();
|
||||
if (oldController) {
|
||||
@@ -63,7 +66,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
|
||||
const processAndSetResults = async (results: SearchResult[], merge = false) => {
|
||||
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(
|
||||
results.map(async (searchResult) => {
|
||||
@@ -75,17 +78,17 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
}
|
||||
} catch (e) {
|
||||
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();
|
||||
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 };
|
||||
})
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
@@ -110,7 +113,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
// Optimization for favorite navigation
|
||||
if (preferredSource && id) {
|
||||
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 preferredSearchError: any = null;
|
||||
@@ -120,52 +123,52 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
preferredResult = response.results;
|
||||
} catch (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();
|
||||
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;
|
||||
|
||||
// 检查preferred source结果
|
||||
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);
|
||||
set({ loading: false });
|
||||
} else {
|
||||
// 降级策略:preferred source失败时立即尝试所有源
|
||||
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 {
|
||||
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();
|
||||
console.info(`[PERF] FALLBACK search (all sources) START - query: "${q}"`);
|
||||
logger.info(`[PERF] FALLBACK search (all sources) START - query: "${q}"`);
|
||||
|
||||
try {
|
||||
const { results: allResults } = await api.searchVideos(q);
|
||||
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);
|
||||
console.info(`[FALLBACK] Filtered results: ${filteredResults.length} matches for "${q}"`);
|
||||
logger.info(`[FALLBACK] Filtered results: ${filteredResults.length} matches for "${q}"`);
|
||||
|
||||
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);
|
||||
set({ loading: false });
|
||||
} else {
|
||||
console.error(`[ERROR] FALLBACK search found no matching results for "${q}"`);
|
||||
logger.error(`[ERROR] FALLBACK search found no matching results for "${q}"`);
|
||||
set({
|
||||
error: `未找到 "${q}" 的播放源,请检查标题或稍后重试`,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error(`[ERROR] FALLBACK search FAILED:`, fallbackError);
|
||||
logger.error(`[ERROR] FALLBACK search FAILED:`, fallbackError);
|
||||
set({
|
||||
error: `搜索失败:${fallbackError instanceof Error ? fallbackError.message : '网络错误,请稍后重试'}`,
|
||||
loading: false
|
||||
@@ -176,39 +179,39 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
// 后台搜索(如果preferred source成功的话)
|
||||
if (preferredResult.length > 0) {
|
||||
const searchAllStart = performance.now();
|
||||
console.info(`[PERF] API searchVideos (background) START`);
|
||||
logger.info(`[PERF] API searchVideos (background) START`);
|
||||
|
||||
try {
|
||||
const { results: allResults } = await api.searchVideos(q);
|
||||
|
||||
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;
|
||||
await processAndSetResults(allResults.filter(item => item.title === q), true);
|
||||
} 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 {
|
||||
// Standard navigation: fetch resources, then fetch details one by one
|
||||
const resourcesStart = performance.now();
|
||||
console.info(`[PERF] API getResources START - query: "${q}"`);
|
||||
logger.info(`[PERF] API getResources START - query: "${q}"`);
|
||||
|
||||
try {
|
||||
const allResources = await api.getResources(signal);
|
||||
|
||||
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
|
||||
? allResources
|
||||
: 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) {
|
||||
console.error(`[ERROR] No enabled resources available for search`);
|
||||
logger.error(`[ERROR] No enabled resources available for search`);
|
||||
set({
|
||||
error: "没有可用的视频源,请检查设置或联系管理员",
|
||||
loading: false
|
||||
@@ -223,22 +226,22 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
const searchStart = performance.now();
|
||||
const { results } = await api.searchVideo(q, resource.key, signal);
|
||||
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) {
|
||||
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);
|
||||
if (!firstResultFound) {
|
||||
set({ loading: false }); // Stop loading indicator on first result
|
||||
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 {
|
||||
console.warn(`[WARN] Source "${resource.name}" returned 0 results for "${q}"`);
|
||||
logger.warn(`[WARN] Source "${resource.name}" returned 0 results for "${q}"`);
|
||||
}
|
||||
} 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) {
|
||||
console.error(`[ERROR] All sources returned 0 results for "${q}"`);
|
||||
logger.error(`[ERROR] All sources returned 0 results for "${q}"`);
|
||||
set({
|
||||
error: `未找到 "${q}" 的播放源,请尝试其他关键词或稍后重试`,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
console.info(`[SUCCESS] Standard search completed, total results: ${totalResults}`);
|
||||
logger.info(`[SUCCESS] Standard search completed, total results: ${totalResults}`);
|
||||
}
|
||||
} catch (resourceError) {
|
||||
console.error(`[ERROR] Failed to get resources:`, resourceError);
|
||||
logger.error(`[ERROR] Failed to get resources:`, resourceError);
|
||||
set({
|
||||
error: `获取视频源失败:${resourceError instanceof Error ? resourceError.message : '网络错误,请稍后重试'}`,
|
||||
loading: false
|
||||
@@ -269,45 +272,45 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
|
||||
// 最终检查:如果所有搜索都完成但仍然没有结果
|
||||
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}" 的播放源,请检查标题拼写或稍后重试` });
|
||||
} 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) {
|
||||
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 {
|
||||
const isFavorited = await FavoriteManager.isFavorited(source, id.toString());
|
||||
set({ isFavorited });
|
||||
console.info(`[INFO] Favorite status: ${isFavorited}`);
|
||||
logger.info(`[INFO] Favorite status: ${isFavorited}`);
|
||||
} catch (favoriteError) {
|
||||
console.warn(`[WARN] Failed to check favorite status:`, favoriteError);
|
||||
logger.warn(`[WARN] Failed to check favorite status:`, favoriteError);
|
||||
}
|
||||
} 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();
|
||||
console.info(`[PERF] Favorite check took ${(favoriteCheckEnd - favoriteCheckStart).toFixed(2)}ms`);
|
||||
logger.info(`[PERF] Favorite check took ${(favoriteCheckEnd - favoriteCheckStart).toFixed(2)}ms`);
|
||||
|
||||
} catch (e) {
|
||||
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 : "获取数据失败";
|
||||
set({ error: `搜索失败:${errorMessage}` });
|
||||
} else {
|
||||
console.info(`[INFO] DetailStore.init aborted by user`);
|
||||
logger.info(`[INFO] DetailStore.init aborted by user`);
|
||||
}
|
||||
} finally {
|
||||
if (!signal.aborted) {
|
||||
set({ loading: false, allSourcesLoaded: true });
|
||||
console.info(`[INFO] DetailStore.init cleanup completed`);
|
||||
logger.info(`[INFO] DetailStore.init cleanup completed`);
|
||||
}
|
||||
|
||||
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);
|
||||
newFailedSources.add(source);
|
||||
|
||||
console.warn(`[SOURCE_FAILED] Marking source "${source}" as failed due to: ${reason}`);
|
||||
console.info(`[SOURCE_FAILED] Total failed sources: ${newFailedSources.size}`);
|
||||
logger.warn(`[SOURCE_FAILED] Marking source "${source}" as failed due to: ${reason}`);
|
||||
logger.info(`[SOURCE_FAILED] Total failed sources: ${newFailedSources.size}`);
|
||||
|
||||
set({ failedSources: newFailedSources });
|
||||
},
|
||||
@@ -355,8 +358,8 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
getNextAvailableSource: (currentSource: string, episodeIndex: number) => {
|
||||
const { searchResults, failedSources } = get();
|
||||
|
||||
console.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] Looking for alternative to "${currentSource}" for episode ${episodeIndex + 1}`);
|
||||
logger.info(`[SOURCE_SELECTION] Failed sources: [${Array.from(failedSources).join(', ')}]`);
|
||||
|
||||
// 过滤掉当前source和已失败的sources
|
||||
const availableSources = searchResults.filter(result =>
|
||||
@@ -366,13 +369,13 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
result.episodes.length > episodeIndex
|
||||
);
|
||||
|
||||
console.info(`[SOURCE_SELECTION] Available sources: ${availableSources.length}`);
|
||||
logger.info(`[SOURCE_SELECTION] Available sources: ${availableSources.length}`);
|
||||
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) {
|
||||
console.error(`[SOURCE_SELECTION] No available sources for episode ${episodeIndex + 1}`);
|
||||
logger.error(`[SOURCE_SELECTION] No available sources for episode ${episodeIndex + 1}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -394,7 +397,7 @@ const useDetailStore = create<DetailState>((set, get) => ({
|
||||
});
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,9 @@ import { AVPlaybackStatus, Video } from "expo-av";
|
||||
import { RefObject } from "react";
|
||||
import { PlayRecord, PlayRecordManager, PlayerSettingsManager } from "@/services/storage";
|
||||
import useDetailStore, { episodesSelectorBySource } from "./detailStore";
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('PlayerStore');
|
||||
|
||||
interface Episode {
|
||||
url: string;
|
||||
@@ -82,17 +85,17 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
|
||||
loadVideo: async ({ source, id, episodeIndex, position, title }) => {
|
||||
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 episodes: string[] = [];
|
||||
|
||||
// 如果有detail,使用detail的source获取episodes;否则使用传入的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());
|
||||
} 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());
|
||||
}
|
||||
|
||||
@@ -101,73 +104,73 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
});
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
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的错误状态
|
||||
const detailStoreState = useDetailStore.getState();
|
||||
if (detailStoreState.error) {
|
||||
console.error(`[ERROR] DetailStore error: ${detailStoreState.error}`);
|
||||
logger.error(`[ERROR] DetailStore error: ${detailStoreState.error}`);
|
||||
set({
|
||||
isLoading: false,
|
||||
// 可以选择在这里设置一个错误状态,但playerStore可能没有error字段
|
||||
});
|
||||
} 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 });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用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());
|
||||
|
||||
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
|
||||
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
|
||||
const sourceWithEpisodes = detailStoreState.searchResults.find(r => r.episodes && r.episodes.length > 0);
|
||||
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;
|
||||
// 更新detail为有episodes的source
|
||||
detail = sourceWithEpisodes;
|
||||
} else {
|
||||
console.error(`[ERROR] No source with episodes found in searchResults`);
|
||||
logger.error(`[ERROR] No source with episodes found in searchResults`);
|
||||
set({ isLoading: false });
|
||||
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 {
|
||||
console.info(`[PERF] Skipping DetailStore.init - using cached data`);
|
||||
logger.info(`[PERF] Skipping DetailStore.init - using cached data`);
|
||||
|
||||
// 即使是缓存的数据,也要确保使用正确的source获取episodes
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -175,31 +178,31 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
|
||||
// 最终验证:确保我们有有效的detail和episodes数据
|
||||
if (!detail) {
|
||||
console.error(`[ERROR] Final check failed: detail is null`);
|
||||
logger.error(`[ERROR] Final check failed: detail is null`);
|
||||
set({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
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 });
|
||||
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 {
|
||||
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 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 storageEnd = performance.now();
|
||||
console.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] PlayerSettingsManager.get took ${(storageEnd - storagePlayRecordEnd).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 savedPlaybackRate = playerSettings?.playbackRate || 1.0;
|
||||
@@ -210,7 +213,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
title: `第 ${index + 1} 集`,
|
||||
}));
|
||||
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({
|
||||
isLoading: false,
|
||||
@@ -223,14 +226,14 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
});
|
||||
|
||||
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) {
|
||||
console.info("Failed to load play record", error);
|
||||
logger.debug("Failed to load play record", error);
|
||||
set({ isLoading: false });
|
||||
|
||||
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 {
|
||||
await videoRef?.current?.replayAsync();
|
||||
} catch (error) {
|
||||
console.info("Failed to replay video:", error);
|
||||
logger.debug("Failed to replay video:", error);
|
||||
Toast.show({ type: "error", text1: "播放失败" });
|
||||
}
|
||||
}
|
||||
@@ -263,7 +266,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
await videoRef?.current?.playAsync();
|
||||
}
|
||||
} catch (error) {
|
||||
console.info("Failed to toggle play/pause:", error);
|
||||
logger.debug("Failed to toggle play/pause:", error);
|
||||
Toast.show({ type: "error", text1: "操作失败" });
|
||||
}
|
||||
}
|
||||
@@ -277,7 +280,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
try {
|
||||
await videoRef?.current?.setPositionAsync(newPosition);
|
||||
} catch (error) {
|
||||
console.info("Failed to seek video:", error);
|
||||
logger.debug("Failed to seek video:", error);
|
||||
Toast.show({ type: "error", text1: "快进/快退失败" });
|
||||
}
|
||||
|
||||
@@ -383,7 +386,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
handlePlaybackStatusUpdate: (newStatus) => {
|
||||
if (!newStatus.isLoaded) {
|
||||
if (newStatus.error) {
|
||||
console.info(`Playback Error: ${newStatus.error}`);
|
||||
logger.debug(`Playback Error: ${newStatus.error}`);
|
||||
}
|
||||
set({ status: newStatus });
|
||||
return;
|
||||
@@ -444,7 +447,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
await PlayerSettingsManager.save(detail.source, detail.id.toString(), { playbackRate: rate });
|
||||
}
|
||||
} 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) => {
|
||||
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 { detail } = detailStoreState;
|
||||
const { currentEpisodeIndex } = get();
|
||||
|
||||
if (!detail) {
|
||||
console.error(`[VIDEO_ERROR] Cannot fallback - no detail available`);
|
||||
logger.error(`[VIDEO_ERROR] Cannot fallback - no detail available`);
|
||||
set({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
@@ -489,7 +492,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
const fallbackSource = useDetailStore.getState().getNextAvailableSource(currentSource, currentEpisodeIndex);
|
||||
|
||||
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({
|
||||
type: "error",
|
||||
text1: "播放失败",
|
||||
@@ -499,7 +502,7 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
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 {
|
||||
// 更新DetailStore的当前detail为fallback source
|
||||
@@ -519,8 +522,8 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
});
|
||||
|
||||
const perfEnd = performance.now();
|
||||
console.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] Successfully switched to fallback source in ${(perfEnd - perfStart).toFixed(2)}ms`);
|
||||
logger.info(`[VIDEO_ERROR] New episode URL: ${newEpisodes[currentEpisodeIndex].substring(0, 100)}...`);
|
||||
|
||||
Toast.show({
|
||||
type: "success",
|
||||
@@ -528,11 +531,11 @@ const usePlayerStore = create<PlayerState>((set, get) => ({
|
||||
text2: `正在使用 ${fallbackSource.source_name}`
|
||||
});
|
||||
} 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 });
|
||||
}
|
||||
} 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 });
|
||||
}
|
||||
},
|
||||
@@ -556,13 +559,13 @@ export const selectCurrentEpisode = (state: PlayerState) => {
|
||||
} else {
|
||||
// 仅在调试模式下打印
|
||||
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 {
|
||||
// 仅在调试模式下打印
|
||||
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;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { create } from 'zustand';
|
||||
import { remoteControlService } from '@/services/remoteControlService';
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('RemoteControlStore');
|
||||
|
||||
interface RemoteControlState {
|
||||
isServerRunning: boolean;
|
||||
@@ -30,23 +33,23 @@ export const useRemoteControlStore = create<RemoteControlState>((set, get) => ({
|
||||
}
|
||||
remoteControlService.init({
|
||||
onMessage: (message: string) => {
|
||||
console.log('[RemoteControlStore] Received message:', message);
|
||||
logger.debug('Received message:', message);
|
||||
const currentState = get();
|
||||
// Use the current targetPage from the store
|
||||
set({ lastMessage: message, targetPage: currentState.targetPage });
|
||||
},
|
||||
onHandshake: () => {
|
||||
console.log('[RemoteControlStore] Handshake successful');
|
||||
logger.debug('Handshake successful');
|
||||
set({ isModalVisible: false })
|
||||
},
|
||||
});
|
||||
try {
|
||||
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 });
|
||||
} catch {
|
||||
const errorMessage = '启动失败,请强制退应用后重试。';
|
||||
console.info('[RemoteControlStore] Failed to start server:', errorMessage);
|
||||
logger.error('Failed to start server:', errorMessage);
|
||||
set({ error: errorMessage });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,6 +2,9 @@ import { create } from "zustand";
|
||||
import { SettingsManager } from "@/services/storage";
|
||||
import { api, ServerConfig } from "@/services/api";
|
||||
import { storageConfig } from "@/services/storageConfig";
|
||||
import Logger from "@/utils/Logger";
|
||||
|
||||
const logger = Logger.withTag('SettingsStore');
|
||||
|
||||
interface SettingsState {
|
||||
apiBaseUrl: string;
|
||||
@@ -65,7 +68,7 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
||||
}
|
||||
} catch (error) {
|
||||
set({ serverConfig: null });
|
||||
console.info("Failed to fetch server config:", error);
|
||||
logger.error("Failed to fetch server config:", error);
|
||||
} finally {
|
||||
set({ isLoadingServerConfig: false });
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { create } from 'zustand';
|
||||
import updateService from '../services/updateService';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import Toast from 'react-native-toast-message';
|
||||
import Logger from '@/utils/Logger';
|
||||
|
||||
const logger = Logger.withTag('UpdateStore');
|
||||
|
||||
interface UpdateState {
|
||||
// 状态
|
||||
@@ -151,7 +154,7 @@ export const useUpdateStore = create<UpdateState>((set, get) => ({
|
||||
// 安装开始后,关闭弹窗
|
||||
set({ showUpdateModal: false });
|
||||
} catch (error) {
|
||||
console.info('安装失败:', error);
|
||||
logger.error('安装失败:', error);
|
||||
set({
|
||||
error: error instanceof Error ? error.message : '安装失败',
|
||||
});
|
||||
@@ -200,6 +203,6 @@ export const initUpdateStore = async () => {
|
||||
skipVersion: skipVersion || null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.info('初始化更新存储失败:', error);
|
||||
logger.error('初始化更新存储失败:', error);
|
||||
}
|
||||
};
|
||||
149
utils/Logger.ts
Normal file
149
utils/Logger.ts
Normal 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;
|
||||
36
yarn.lock
36
yarn.lock
@@ -3064,6 +3064,11 @@ babel-plugin-transform-flow-enums@^0.0.2:
|
||||
dependencies:
|
||||
"@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:
|
||||
version "1.1.0"
|
||||
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"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
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:
|
||||
"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==
|
||||
@@ -8927,7 +8923,7 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -8941,13 +8937,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "7.1.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@@ -9797,15 +9786,6 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.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:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
||||
Reference in New Issue
Block a user