diff --git a/app/_layout.tsx b/app/_layout.tsx index 5675332..c3cd3b8 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -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]); diff --git a/app/play.tsx b/app/play.tsx index 3e9a8f7..b90a4c7 100644 --- a/app/play.tsx +++ b/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]); diff --git a/app/search.tsx b/app/search.tsx index 9ae719e..ace7be8 100644 --- a/app/search.tsx +++ b/app/search.tsx @@ -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); } diff --git a/babel.config.js b/babel.config.js index df63172..29fc564 100644 --- a/babel.config.js +++ b/babel.config.js @@ -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, }; }; \ No newline at end of file diff --git a/components/ResponsiveVideoCard.tsx b/components/ResponsiveVideoCard.tsx index 101a714..b1e31e0 100644 --- a/components/ResponsiveVideoCard.tsx +++ b/components/ResponsiveVideoCard.tsx @@ -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 { id: string; @@ -138,7 +141,7 @@ const ResponsiveVideoCard = forwardRef( router.replace("/"); } } catch (error) { - console.info("Failed to delete play record:", error); + logger.info("Failed to delete play record:", error); Alert.alert("错误", "删除观看记录失败,请重试"); } }, diff --git a/components/SourceSelectionModal.tsx b/components/SourceSelectionModal.tsx index a5ced62..6b39943 100644 --- a/components/SourceSelectionModal.tsx +++ b/components/SourceSelectionModal.tsx @@ -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); diff --git a/components/VideoCard.mobile.tsx b/components/VideoCard.mobile.tsx index dfa2dca..bc36c7f 100644 --- a/components/VideoCard.mobile.tsx +++ b/components/VideoCard.mobile.tsx @@ -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 { id: string; @@ -97,7 +100,7 @@ const VideoCardMobile = forwardRef( 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("错误", "删除观看记录失败,请重试"); } }, diff --git a/components/VideoCard.tablet.tsx b/components/VideoCard.tablet.tsx index dc4062b..1573f42 100644 --- a/components/VideoCard.tablet.tsx +++ b/components/VideoCard.tablet.tsx @@ -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 { id: string; @@ -119,7 +122,7 @@ const VideoCardTablet = forwardRef( 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("错误", "删除观看记录失败,请重试"); } }, diff --git a/components/VideoCard.tv.tsx b/components/VideoCard.tv.tsx index eb7882f..426c779 100644 --- a/components/VideoCard.tv.tsx +++ b/components/VideoCard.tv.tsx @@ -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 { id: string; @@ -131,7 +134,7 @@ const VideoCard = forwardRef( router.replace("/"); } } catch (error) { - console.info("Failed to delete play record:", error); + logger.info("Failed to delete play record:", error); Alert.alert("错误", "删除观看记录失败,请重试"); } }, diff --git a/package.json b/package.json index 0267af2..1e0a111 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/services/api.ts b/services/api.ts index 7d653dc..4cd0ec3 100644 --- a/services/api.ts +++ b/services/api.ts @@ -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 []; } diff --git a/services/m3u.ts b/services/m3u.ts index be5c74c..a3a9cba 100644 --- a/services/m3u.ts +++ b/services/m3u.ts @@ -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 => { 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 } }; diff --git a/services/m3u8.ts b/services/m3u8.ts index 2fe11c6..95de58b 100644 --- a/services/m3u8.ts +++ b/services/m3u8.ts @@ -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 => { 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; } }; diff --git a/services/remoteControlService.ts b/services/remoteControlService.ts index b5f7868..a401632 100644 --- a/services/remoteControlService.ts +++ b/services/remoteControlService.ts @@ -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 = () => {