commit 7e6095d2bbeb10b85f515b181c9551096ce3ad3b Author: zimplexing Date: Fri Jun 27 16:16:14 2025 +0800 Update diff --git a/.cursor/rules/common.mdc b/.cursor/rules/common.mdc new file mode 100644 index 0000000..f202b78 --- /dev/null +++ b/.cursor/rules/common.mdc @@ -0,0 +1,7 @@ +--- +description: +globs: +alwaysApply: true +--- +- 这是一个react-native android tv项目 +- 请不要修改web/目录下的文件 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f837479 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +node_modules/ +.expo/ +dist/ +ios/ +android/ +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +web-build/ + +# macOS +.DS_Store + +# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb +# The following patterns were generated by expo-cli + +expo-env.d.ts +# @end expo-cli \ No newline at end of file diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md new file mode 100644 index 0000000..269f247 --- /dev/null +++ b/MIGRATION_PLAN.md @@ -0,0 +1,104 @@ +# Web 功能至 React Native TV 应用迁移计划 + +## 1. 项目目标 + +将现有 `web` 项目 (基于 Next.js) 的核心功能,包括视频浏览、搜索、详情查看、播放记录和收藏,完整地迁移到 React Native TV 应用中,并为电视遥控器交互进行深度优化。 + +## 2. 核心策略 + +我们将采用**前后端分离**的核心策略: + +- **后端服务**: 将 `web/src/app/api/` 中的所有 API 逻辑剥离出来,封装成一个独立的、使用 **Express.js** 构建的 Node.js 服务。该服务将通过 **Docker** 进行容器化,部署在您的私有服务器上。 +- **TV 应用 (前端)**: React Native TV 应用将作为纯前端,负责 UI 展示和用户交互。它将通过网络请求调用上述独立的后端服务来获取和提交数据。 +- **数据持久化**: 用户数据(播放记录、收藏等)将从 `localStorage` 迁移到 React Native 的 **`AsyncStorage`**,实现应用内的本地持久化存储。 + +## 3. 架构设计 + +迁移后的系统架构如下所示: + +```mermaid +graph TD + subgraph "React Native TV 应用" + A[用户] -->|遥控器交互| B(TV 应用界面) + B -->|数据请求| C{数据服务层} + end + + subgraph "本地存储" + C -->|读/写| D[AsyncStorage (播放记录, 收藏)] + end + + subgraph "后端服务 (Docker @ 您的服务器)" + E[Express.js API 服务] + end + + subgraph "第三方数据源" + F[视频源 API] + G[豆瓣 API] + end + + C -->|API 请求| E + E -->|代理请求| F + E -->|代理请求| G +``` + +## 4. 分阶段实施计划 + +我们将迁移过程分为四个主要阶段,循序渐进,以确保每个环节都稳固可靠。 + +### 阶段一:后端服务搭建与部署 (Backend First) + +**目标**: 优先保障数据接口的可用性,为后续所有前端工作提供数据基础。 + +- **任务 1**: 在项目根目录创建 `backend/` 文件夹,并初始化一个 Node.js (Express.js) 项目。 +- **任务 2**: 将 `web/src/app/api/` 下的所有 API (search, detail, douban, login, playrecords) 的逻辑迁移到 Express 路由中。 +- **任务 3**: 编写一个高效的 `Dockerfile`,用于构建后端服务的生产环境镜像。 +- **任务 4**: 提供部署指南,说明如何在您的服务器上通过 Docker 启动服务,并配置必要的环境变量(如端口)。 +- **交付物**: 可独立运行和测试的后端 API 服务。 + +### 阶段二:TV 应用基础建设与数据层迁移 + +**目标**: 在 TV 应用中搭建好基础框架,并完成数据存储逻辑的替换。 + +- **任务 1**: 安装必要的依赖库:`@react-native-async-storage/async-storage` (用于本地存储) 和 `react-navigation` (用于页面路由)。 +- **任务 2**: 在 TV 应用中创建 `services/api.ts` 文件,封装所有对新后端服务的 `fetch` 请求。 +- **任务 3**: 在 TV 应用中创建 `services/storage.ts` 文件,使用 `AsyncStorage` 重新实现 `web/src/lib/db.client.ts` 中的所有功能(播放记录、收藏的增删改查)。 +- **交付物**: 一个可以与后端通信并能在本地存取数据的 TV 应用骨架。 + +### 阶段三:核心 UI 组件库迁移 + +**目标**: 自底向上,将 Web 组件重构为适用于 TV 平台的原生组件。 + +- **任务 1**: 识别 `web/src/components` 中的核心可复用组件,如 `VideoCard`、`ScrollableRow`、`AggregateCard` 等。 +- **任务 2**: 在 TV 项目的 `components/` 目录下,创建对应的 TV 版本组件。 + - 将 HTML 标签 (`div`, `img`) 替换为 React Native 组件 (`View`, `Image`)。 + - 将 CSS 样式转换为 React Native 的 `StyleSheet` API。 + - **关键**: 为所有可交互组件添加 `onFocus`, `onBlur` 事件处理,并使用 `Touchable` 组件实现遥控器响应,确保良好的焦点管理和导航体验。 +- **交付物**: 一套专为 TV 优化的、可复用的基础 UI 组件库。 + +### 阶段四:页面路由与功能整合 + +**目标**: 组装所有模块,将应用串联成一个完整、可用的产品。 + +- **任务 1**: 使用 `React Navigation` 库搭建 App 的整体路由结构,需要实现查看更多,详情页面,播放页等 +- **任务 2**: 创建所有核心页面 (Screens),例如 `MoreScreen`, `SearchScreen`, `DetailScreen`, `PlayerScreen`。 +- **任务 3**: 在各个页面中,集成前几个阶段完成的 `services` 和 `components`,调用后端 API 获取数据,并渲染出最终界面。 +- **任务 4**: 对整体应用进行联调测试,重点测试遥控器导航的流畅性和功能的完整性。 +- **交付物**: 功能完整的 React Native TV 应用初版。 + +## 5. 技术栈清单 + +- **TV 应用**: React Native, TypeScript, React Navigation +- **后端服务**: Node.js, Express.js +- **数据存储**: @react-native-async-storage/async-storage +- **部署**: Docker + +## 6. 风险与应对 + +- **风险**: 第三方 API 接口变更或失效。 + - **应对**: 后端服务已将 API 请求集中处理,若有变更,只需修改后端代码,无需更新客户端 App。 +- **风险**: TV 平台(Android TV, tvOS)的 UI/UX 兼容性差异。 + - **应对**: 在开发过程中,优先使用 React Native 官方推荐的跨平台组件。对特定平台的差异,可以使用 `Platform.select()` 进行适配。 + +## 7. 下一步 + +我们已就此计划达成共识。下一步建议是:**切换到“代码”模式**,并开始着手执行**阶段一**的任务,即搭建和迁移独立的后端服务。 diff --git a/Plan.md b/Plan.md new file mode 100644 index 0000000..e242d9a --- /dev/null +++ b/Plan.md @@ -0,0 +1,417 @@ +# MoonTV 到 MyTv 迁移可行性分析与方案 + +## 项目概述 + +### MoonTV 项目分析 + +**项目定位**: 基于 Next.js 的 Web 端影视聚合播放器 + +**核心技术栈**: + +- **前端框架**: Next.js 14 (App Router) +- **UI 库**: Tailwind CSS + React +- **语言**: TypeScript +- **视频播放器**: ArtPlayer + HLS.js +- **状态管理**: React Hooks + LocalStorage +- **部署**: Docker + Vercel + +**核心功能**: + +1. 多源影视资源聚合搜索 +2. 在线视频播放 (支持 HLS 流) +3. 影片详情展示 (演员、年份、简介等) +4. 收藏功能 (LocalStorage) +5. 播放记录 (继续观看) +6. 响应式布局 (桌面 + 移动端) +7. PWA 支持 + +**项目结构**: + +``` +MoonTV/ +├── src/ +│ ├── app/ # Next.js 14 App Router 页面 +│ │ ├── (pages)/ # 主要页面路由 +│ │ ├── api/ # 后端 API 路由 +│ │ │ ├── douban/ # 豆瓣 API 集成 +│ │ │ ├── search/ # 多源搜索 API +│ │ │ ├── detail/ # 影片详情 API +│ │ │ └── login/ # 认证 API +│ │ └── globals.css # 全局样式 +│ ├── components/ # React 组件 +│ │ ├── VideoCard.tsx # 视频卡片组件 +│ │ ├── AggregateCard.tsx # 聚合卡片组件 +│ │ ├── ScrollableRow.tsx # 横向滚动容器 +│ │ └── ... +│ └── lib/ # 工具库 +│ ├── types.ts # TypeScript 类型定义 +│ ├── db.client.ts # 本地存储操作 +│ └── config.ts # 配置管理 +├── config.json # 影视资源站点配置 +└── package.json # 项目依赖 +``` + +### MyTv 项目分析 + +**项目定位**: 基于 React Native TV 的电视端应用 + +**核心技术栈**: + +- **框架**: React Native TV (支持 Apple TV + Android TV) +- **导航**: Expo Router (文件系统路由) +- **视频播放**: react-native-media-console +- **配置**: @react-native-tvos/config-tv +- **语言**: TypeScript + +**核心功能**: + +1. TV 遥控器导航支持 +2. 焦点管理和高亮显示 +3. 视频播放 (react-native-video) +4. TV 专用 UI 组件 +5. 跨平台支持 (Apple TV + Android TV) + +## 迁移可行性分析 + +### ✅ 高度可行的方面 + +#### 1. **核心业务逻辑** + +- **数据结构兼容**: MoonTV 的 `VideoDetail` 类型定义可直接复用 +- **API 接口复用**: MoonTV 的搜索、详情 API 可通过网络请求在 TV 端调用 +- **业务流程一致**: 搜索 → 详情 → 播放的核心流程完全匹配 + +#### 2. **功能特性映射** + +| MoonTV 功能 | MyTv 对应功能 | 迁移难度 | +| ----------- | ------------------ | --------- | +| 多源搜索 | 网络 API 调用 | ⭐ 容易 | +| 影片详情 | 详情页面适配 | ⭐⭐ 中等 | +| 视频播放 | react-native-video | ⭐⭐ 中等 | +| 收藏功能 | AsyncStorage | ⭐ 容易 | +| 播放记录 | AsyncStorage | ⭐ 容易 | + +#### 3. **技术栈兼容性** + +- **TypeScript**: 两个项目都使用 TypeScript,类型定义可复用 +- **React 生态**: 核心 React 概念一致,组件逻辑可借鉴 +- **状态管理**: 都使用 Hooks,状态逻辑可迁移 + +### ⚠️ 需要重点关注的挑战 + +#### 1. **UI/UX 差异** + +- **交互模式**: Web 鼠标/触摸 → TV 遥控器 D-Pad 导航 +- **布局设计**: 响应式网页布局 → TV 固定尺寸界面 +- **焦点管理**: Web 无需焦点 → TV 必须精确控制焦点流转 + +#### 2. **视频播放器差异** + +- **MoonTV**: ArtPlayer (Web) + HLS.js +- **MyTv**: react-native-media-console + react-native-video +- **挑战**: 播放控制、进度管理、字幕等功能需要重新实现 + +#### 3. **存储机制差异** + +- **MoonTV**: LocalStorage (同步) +- **MyTv**: AsyncStorage (异步) +- **影响**: 所有本地存储操作需要异步化改造 + +### ❌ 不可直接迁移的方面 + +#### 1. **Web 特定功能** + +- PWA 功能 (离线缓存、安装到桌面) +- 豆瓣 API 的 CORS 处理 +- 浏览器特定的媒体 API + +#### 2. **部署和分发** + +- Docker/Vercel 部署 → App Store/Google Play 发布 +- Web 更新 → 应用商店审核流程 + +## 迁移方案设计 + +### 方案 A: 渐进式迁移 (推荐) + +#### 阶段 1: 基础架构搭建 (1-2 周) + +1. **创建 TV 端项目结构** + + ```typescript + MyTv/ + ├── app/ + │ ├── (tabs)/ + │ │ ├── home.tsx # 首页 - 影片推荐 + │ │ ├── search.tsx # 搜索页 + │ │ ├── library.tsx # 我的收藏/播放记录 + │ │ └── settings.tsx # 设置页 + │ ├── detail/ + │ │ └── [id].tsx # 影片详情页 + │ └── play/ + │ └── [id].tsx # 播放页面 + ├── components/ + │ ├── TVVideoCard.tsx # TV 适配的视频卡片 + │ ├── TVSearchInput.tsx # TV 搜索输入 + │ ├── TVMediaPlayer.tsx # TV 媒体播放器 + │ └── FocusableView.tsx # 可获取焦点的容器 + ├── services/ + │ ├── api.ts # MoonTV API 调用封装 + │ ├── storage.ts # AsyncStorage 封装 + │ └── player.ts # 播放器控制逻辑 + └── types/ + └── index.ts # 从 MoonTV 迁移的类型定义 + ``` + +2. **核心服务层迁移** + + ```typescript + // services/api.ts - 调用 MoonTV 后端 + class MoonTVAPI { + private baseURL = "https://your-moontv-instance.com/api"; + + async search(query: string): Promise { + const response = await fetch(`${this.baseURL}/search?q=${query}`); + return response.json(); + } + + async getDetail(source: string, id: string): Promise { + const response = await fetch( + `${this.baseURL}/detail?source=${source}&id=${id}` + ); + return response.json(); + } + } + ``` + +3. **存储层适配** + + ```typescript + // services/storage.ts + import AsyncStorage from "@react-native-async-storage/async-storage"; + + export class TVStorage { + static async getFavorites(): Promise { + const data = await AsyncStorage.getItem("favorites"); + return data ? JSON.parse(data) : []; + } + + static async addFavorite(videoId: string): Promise { + const favorites = await this.getFavorites(); + if (!favorites.includes(videoId)) { + favorites.push(videoId); + await AsyncStorage.setItem("favorites", JSON.stringify(favorites)); + } + } + } + ``` + +#### 阶段 2: 核心页面开发 (2-3 周) + +1. **搜索功能实现** + + ```typescript + // app/(tabs)/search.tsx + export default function SearchScreen() { + const [query, setQuery] = useState(""); + const [results, setResults] = useState([]); + const [focusedIndex, setFocusedIndex] = useState(0); + + const handleSearch = async (text: string) => { + const searchResults = await MoonTVAPI.search(text); + setResults(searchResults); + }; + + return ( + + + ( + setFocusedIndex(index)} + /> + )} + keyExtractor={(item) => item.id} + /> + + ); + } + ``` + +2. **详情页面开发** + ```typescript + // app/detail/[id].tsx + export default function DetailScreen() { + const { id, source } = useLocalSearchParams(); + const [detail, setDetail] = useState(null); + + useEffect(() => { + MoonTVAPI.getDetail(source as string, id as string).then(setDetail); + }, [source, id]); + + return ( + + + + + + ); + } + ``` + +#### 阶段 3: 播放功能集成 (1-2 周) + +1. **播放器组件开发** + + ```typescript + // components/TVMediaPlayer.tsx + import VideoPlayer from "react-native-media-console"; + + export function TVMediaPlayer({ source, title }: TVMediaPlayerProps) { + return ( + + ); + } + ``` + +2. **播放页面开发** + ```typescript + // app/play/[id].tsx + export default function PlayScreen() { + const { videoUrl, title } = useLocalSearchParams(); + + return ( + + + + ); + } + ``` + +#### 阶段 4: TV 专用优化 (1-2 周) + +1. **焦点导航优化** + + ```typescript + // components/FocusableView.tsx + export function FocusableView({ children, onFocus, onBlur }: Props) { + return ( + [styles.container, focused && styles.focused]} + > + {children} + + ); + } + ``` + +2. **遥控器快捷键支持** + ```typescript + // hooks/useRemoteControl.ts + export function useRemoteControl() { + useEffect(() => { + const handleKeyPress = (event: any) => { + switch (event.key) { + case "ArrowUp": // 遥控器上键 + // 处理向上导航 + break; + case "ArrowDown": // 遥控器下键 + // 处理向下导航 + break; + case "Select": // 遥控器确定键 + // 处理选择 + break; + } + }; + + // 注册事件监听器 + }, []); + } + ``` + +### 方案 B: API 服务复用 (备选) + +如果不想完全迁移前端,可以保持 MoonTV 作为 API 服务器,MyTv 作为纯客户端: + +```typescript +// MyTv 通过网络调用 MoonTV 的 API +const MOONTV_API_BASE = "https://your-moontv-instance.com/api"; + +class MoonTVClient { + async search(query: string) { + return fetch(`${MOONTV_API_BASE}/search?q=${query}`); + } + + async getVideoDetail(source: string, id: string) { + return fetch(`${MOONTV_API_BASE}/detail?source=${source}&id=${id}`); + } +} +``` + +## 实施建议 + +### 开发优先级 + +1. **高优先级** (核心功能) + + - 基础 TV 导航框架 + - 视频搜索和播放 + - 遥控器支持 + +2. **中优先级** (用户体验) + + - 收藏和播放记录 + - 详情页面丰富化 + - 性能优化 + +3. **低优先级** (增值功能) + - 个性化推荐 + - 多用户支持 + - 云同步 + +### 技术风险与缓解 + +| 风险 | 影响 | 缓解措施 | +| --------------- | ------------ | -------------------------- | +| TV 焦点管理复杂 | 开发周期延长 | 早期原型验证,分步实现 | +| 视频播放兼容性 | 功能不稳定 | 多设备测试,备用播放器方案 | +| API 调用延迟 | 用户体验差 | 本地缓存,异步加载 | +| 应用商店审核 | 发布延期 | 提前了解审核规则,合规设计 | + +### 开发资源估算 + +- **开发时间**: 6-8 周 (1-2 人团队) +- **技术栈学习**: React Native TV (1-2 周) +- **UI/UX 设计**: TV 界面设计 (1 周) +- **测试验证**: 多设备兼容性 (1 周) + +## 结论 + +**迁移可行性**: ⭐⭐⭐⭐☆ (高度可行) + +**推荐方案**: 渐进式迁移,保持 MoonTV 作为后端 API 服务,开发独立的 MyTv 客户端 + +**关键成功因素**: + +1. 合理的架构设计 (分离 API 和 UI) +2. TV 交互模式的深度理解 +3. 充分的设备兼容性测试 +4. 渐进式开发和验证 + +通过这个方案,可以充分利用 MoonTV 的成熟后端能力,同时为 TV 平台提供原生的使用体验。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..56afc84 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# react-native-media-console TV demo 👋 + +![Apple TV screen shot](https://github.com/douglowder/examples/assets/6577821/a881466f-a7a0-4c66-b1fc-33235c466997) +![Android TV screen shot](https://github.com/douglowder/examples/assets/6577821/815c8e01-8275-4cc1-bd57-b9c8bce1fb02) + +This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). + +This project uses + +- the [React Native TV fork](https://github.com/react-native-tvos/react-native-tvos), which supports both phone (Android and iOS) and TV (Android TV and Apple TV) targets +- the [React Native TV config plugin](https://github.com/react-native-tvos/config-tv/tree/main/packages/config-tv) to allow Expo prebuild to modify the project's native files for TV builds +- [react-native-video](https://github.com/TheWidlarzGroup/react-native-video) package to play videos +- [react-native-media-console](https://github.com/LunatiqueCoder/react-native-media-console) for video controls + +## 🚀 How to use + +- `cd` into the project + +```sh +yarn +yarn prebuild # Executes Expo prebuild with TV modifications +yarn ios # Build and run for Apple TV +yarn android # Build for Android TV +``` + +## Development + +You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). + +This project includes a [demo](./components/EventHandlingDemo.tsx) showing how to use React Native TV APIs to highlight controls as the user navigates the screen with the remote control. + +## TV specific file extensions + +This project includes an [example Metro configuration](./metro.config.js) that allows Metro to resolve application source files with TV-specific code, indicated by specific file extensions (`*.ios.tv.tsx`, `*.android.tv.tsx`, `*.tv.tsx`). The [ExternalLink](./components/ExternalLink.tsx) component makes use of this by having a [separate TV source file](./components/ExternalLink.tv.tsx) that avoids importing packages that don't exist on Apple TV. + +## Learn more + +To learn more about developing your project with Expo, look at the following resources: + +- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). +- [Learn Expo tutorial](https://docs.expo.dev/learn): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. + +## Join the community + +Join our community of developers creating universal apps. + +- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. +- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. diff --git a/app.json b/app.json new file mode 100644 index 0000000..58b359e --- /dev/null +++ b/app.json @@ -0,0 +1,45 @@ +{ + "expo": { + "plugins": [ + [ + "@react-native-tvos/config-tv", + { + "androidTVBanner": "./assets/tv_icons/icon-400x240.png", + "appleTVImages": { + "icon": "./assets/tv_icons/icon-1280x768.png", + "iconSmall": "./assets/tv_icons/icon-400x240.png", + "iconSmall2x": "./assets/tv_icons/icon-800x480.png", + "topShelf": "./assets/tv_icons/icon-1920x720.png", + "topShelf2x": "./assets/tv_icons/icon-3840x1440.png", + "topShelfWide": "./assets/tv_icons/icon-2320x720.png", + "topShelfWide2x": "./assets/tv_icons/icon-4640x1440.png" + } + } + ], + [ + "expo-build-properties", + { + "ios": { + "newArchEnabled": false + }, + "android": { + "newArchEnabled": false + } + } + ], + "expo-router" + ], + "experiments": { + "typedRoutes": true + }, + "name": "MyTVProject", + "slug": "MyTVProject", + "android": { + "package": "com.tvproject" + }, + "ios": { + "bundleIdentifier": "com.tvproject" + }, + "scheme": "tvproject" + } +} diff --git a/app/+html.tsx b/app/+html.tsx new file mode 100644 index 0000000..3bd8155 --- /dev/null +++ b/app/+html.tsx @@ -0,0 +1,42 @@ +import {ScrollViewStyleReset} from 'expo-router/html'; +import {type PropsWithChildren} from 'react'; + +/** + * This file is web-only and used to configure the root HTML for every web page during static rendering. + * The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs. + */ +export default function Root({children}: PropsWithChildren) { + return ( + + + + + + + {/* + Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. + However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. + */} + + + {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} +