Files
JKVideo/CLAUDE.md
2026-03-19 16:34:56 +08:00

227 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# reactBilibiliApp — 项目架构与技术要点
## 项目概述
仿 B 站 React Native 客户端,使用 Expo SDK 55 + expo-router调用 Bilibili 官方 Web API 获取热门视频、直播、视频详情、弹幕、评论、搜索及扫码登录。
## 技术栈
| 层 | 技术 |
|---|---|
| 框架 | React Native 0.83 + Expo SDK 55 |
| 路由 | expo-router v4文件系统路由Stack 导航) |
| 状态管理 | Zustand |
| 网络请求 | Axios |
| 本地存储 | @react-native-async-storage/async-storage |
| 视频播放(详情页/直播) | react-native-videoDASH/HLS/MP4|
| 视频播放(首页 BigCard | react-native-videoDASH MPD内联自动播放 |
| 视频播放WebView 降级) | react-native-webviewNativeVideoPlayer |
| 页面滑动 | react-native-pager-view首页热门/直播 Tab |
| 图标 | @expo/vector-iconsIonicons |
## 目录结构
```
app/
_layout.tsx # 根布局Stack 导航 + 启动时恢复登录态/下载列表/设置
# 全局挂载 <MiniPlayer />
index.tsx # 首页PagerView 切换热门/直播;绝对定位悬浮导航栏
video/
_layout.tsx # Stack 导航(无头部)
[bvid].tsx # 视频详情页(播放 + 简介/评论/弹幕 三 Tab + 推荐视频 + 下载)
live/
_layout.tsx # Stack 导航(无头部)
[roomId].tsx # 直播详情页HLS/FLV 播放 + 简介/弹幕 Tab
search.tsx # 搜索页(关键词搜索视频)
downloads.tsx # 下载管理页(已下载视频列表)
settings.tsx # 设置页(封面图清晰度 + 退出登录)
components/
VideoCard.tsx # 双列视频卡片封面、标题、UP主、播放量读 settingsStore 决定图片质量
BigVideoCard.tsx # 全宽视频卡片(首页热门精选),内联 react-native-video 自动播放,
# 支持静音/取消静音、水平滑动快进、进度条/缓冲条
LiveCard.tsx # 直播房间卡片(封面、主播、在线人数、直播状态脉冲点)
LivePulse.tsx # 直播红点动画组件
VideoPlayer.tsx # 视频播放器入口web → <video>native → react-native-video (DASH/MP4)
# 支持多清晰度切换
NativeVideoPlayer.tsx# WebView 内嵌 HTML5 videoJS 注入 src降级方案
LivePlayer.tsx # 直播播放器react-native-video HLS多画质切换
DanmakuList.tsx # 弹幕列表面板(视频/直播通用);支持逐行逐帧 drip 渲染、
# 舰长标记、礼物计数、实时直播弹幕追加
DanmakuOverlay.tsx # 悬浮弹幕层(飘屏弹幕,用于视频播放器覆盖层)
MiniPlayer.tsx # 全局迷你播放器(底部悬浮,切页后继续播放)
CommentItem.tsx # 评论条目
LoginModal.tsx # 扫码登录 Modal底部弹出
DownloadSheet.tsx # 下载选择面板(选清晰度后触发下载)
DownloadProgressBtn.tsx # 导航栏下载进度按钮(显示正在下载数量)
hooks/
useVideoList.ts # 热门视频 + 首页直播轮播分页加载(下拉刷新、上拉加载更多)
useLiveList.ts # 直播列表分页加载(按分区过滤)
useVideoDetail.ts # 视频详情 + 获取播放流 URL支持多清晰度 DASH/MP4
useLiveDetail.ts # 直播间详情 + HLS/FLV 流地址(支持画质切换)
useLiveDanmaku.ts # 直播弹幕 WebSocket 实时接收
useComments.ts # 评论分页加载(热评/最新排序)
useRelatedVideos.ts # 推荐视频流(视频详情页简介 Tab 下方)
useSearch.ts # 搜索钩子(分页加载搜索结果)
useDownload.ts # 下载任务管理
services/
bilibili.ts # 所有 API 请求axios 实例 + Cookie 拦截器)
types.ts # 数据类型定义
store/
authStore.ts # Zustand 登录状态sessdata/uid/username/face + 持久化)
downloadStore.ts # Zustand 下载任务列表(持久化)
settingsStore.ts # Zustand 用户设置coverQuality: 'hd'|'normal' + 持久化)
videoStore.ts # Zustand 当前播放视频状态(供 MiniPlayer 使用)
utils/
format.ts # formatCount / formatDuration / formatTime
imageUrl.ts # proxyImageUrlWeb 代理绕防盗链)/ coverImageUrl清晰度参数
dash.ts # buildDashMpdUri将 B 站 DASH 接口数据转换为 MPD 文件 URI
danmaku.ts # danmakuColorToCss弹幕颜色转换
```
## 路由结构
- `/` → 首页Stack 根页面PagerView 热门/直播 Tab
- `/video/[bvid]` → 视频详情Stack pushslide_from_right 动画)
- `/live/[roomId]` → 直播详情Stack pushslide_from_right 动画)
- `/search` → 搜索页Stack push
- `/downloads` → 下载管理Stack push
- `/settings` → 设置Stack push已登录时从头像按钮进入
## 首页结构index.tsx
- **PagerView** 容纳热门列表page 0和直播列表page 1`scrollEnabled: false`
- **绝对定位悬浮导航栏**Header头像/搜索框/下载按钮)+ Tab 行(热门/直播),随内容滚动收起
- **热门列表**`toListRows()` 将视频分为 `big`(全宽 BigVideoCard`live`(直播推荐)、`pair`(双列 VideoCard三种行类型
- **直播列表**:横向分区过滤 ScrollView + 双列 LiveCard 网格,支持按分区 ID 切换
- 点击已激活 Tab 会滚动到顶并刷新数据
## BigVideoCard 内联视频components/BigVideoCard.tsx
- 使用 `react-native-video` 直接播放 DASHfnval=16buildDashMpdUri 转 MPD
- 可见时自动播放(静音),不可见时暂停并恢复封面
- 水平滑动手势PanResponder实现快进/快退,显示时间标签
- 进度条 + 缓冲条;静音/取消静音按钮
- 封面图质量受 `settingsStore.coverQuality` 控制
## Bilibili API 关键点
### axios 实例services/bilibili.ts
- 统一携带 `Referer: https://www.bilibili.com``User-Agent`(模拟 Chrome
- 请求拦截器自动注入 Cookie`buvid3`(随机生成并持久化)+ `SESSDATA`(登录后)
### 主要接口
| 函数 | 接口 | 说明 |
|---|---|---|
| `getPopularVideos(pn)` | `/x/web-interface/popular` | 热门视频,每页 20 条 |
| `getVideoDetail(bvid)` | `/x/web-interface/view` | 视频详情(含 ugc_season 合集) |
| `getPlayUrl(bvid, cid, fnval)` | `/x/player/playurl` | 获取播放流fnval=16 DASHfnval=0 MP4 |
| `getRecommendFeed(idx)` | `/x/web-interface/index/top/rcmd` | 推荐视频流(详情页下方) |
| `getComments(aid, pn, sort)` | `/x/v2/reply` | 评论列表sort=2 热评sort=0 最新) |
| `getDanmaku(cid)` | `/x/v1/dm/list.so` | 弹幕列表XML 格式,返回 DanmakuItem[] |
| `getLiveRoomList(areaId, page)` | live 域名 | 直播列表(按分区) |
| `getLiveDetail(roomId)` | live 域名 | 直播间详情 + 主播信息 |
| `getLiveStream(roomId, qn)` | live 域名 | HLS/FLV 直播流地址 |
| `searchVideos(keyword, pn)` | `/x/web-interface/search/all/v2` | 视频搜索 |
| `generateQRCode()` | passport 域名 | 生成登录二维码 |
| `pollQRCode(key)` | passport 域名 | 轮询扫码状态2s 间隔) |
### 播放流参数(重要)
```ts
// DASHreact-native-videodev build / 生产包)
params: { bvid, cid, qn: 112, fnval: 16, platform: 'pc' }
// → 返回 dash 字段,需 buildDashMpdUri 转为本地 MPD
// MP4WebView 降级 / Expo Go
params: { bvid, cid, qn: 64, fnval: 0, platform: 'html5' }
// → 返回 durl[0].url (MP4)
```
- `fnval: 16` → DASH`dash.video[]` + `dash.audio[]`,由 `buildDashMpdUri` 生成 MPD
- `fnval: 0` + `platform: 'html5'` → MP4`durl[0].url`
- `fnval: 1` → FLV不可用HTML5/WebView 不支持)
- `qn: 112` = 1080P+`qn: 80` = 1080P`qn: 64` = 720P未登录时 B 站可能降级
## 视频播放架构
### 视频详情页VideoPlayer
```
VideoPlayer
├── web (Platform.OS === 'web') → 原生 <video> 标签
└── native → react-native-video (DASH MPD / MP4)
支持多清晰度切换、弹幕覆盖层DanmakuOverlay
降级NativeVideoPlayer (WebView + JS 注入)
```
### 首页 BigVideoCard
```
BigVideoCard
└── react-native-videoDASH MPD静音自动播放
+ PanResponder 手势快进
+ 封面 Animated.View 淡出过渡
```
### 直播LivePlayer
```
LivePlayer
└── react-native-videoHLS m3u8 流)
支持画质切换(触发重新获取流地址)
```
## 弹幕系统
- **视频弹幕**`getDanmaku(cid)` 获取全量弹幕,按时间戳 drip 渲染到 `DanmakuList`
- **直播弹幕**`useLiveDanmaku(roomId)` 通过 WebSocket 实时接收支持礼物计数giftCounts
- **弹幕覆盖**`DanmakuOverlay` 悬浮于播放器之上,飘屏滚动
- DanmakuList 支持 `isLive` 模式(实时追加新弹幕,保留最近 500 条)
## 下载功能
- `DownloadSheet`:选择清晰度后调用 `useDownload` 触发后台下载
- `downloadStore`:持久化下载任务列表(进度、状态、本地路径)
- `DownloadProgressBtn`:导航栏图标,显示正在下载的任务数量
- `downloads.tsx`:下载管理页,列出已完成/进行中任务,支持播放和删除
## 登录流程
1. 未登录时右上角头像图标 → 弹出 `LoginModal`;已登录时跳转 `/settings`
2. 调用 `generateQRCode()` 获取 `qrcode_key` + 二维码 URL
3.`https://api.qrserver.com` 渲染二维码图片(第三方服务)
4. 每 2s 轮询 `pollQRCode``code === 0` 时从响应 Header `set-cookie` 提取 `SESSDATA`
5. `useAuthStore.login()``SESSDATA` 写入 AsyncStorage 并更新 Zustand 状态
6. 启动时 `_layout.tsx` 调用 `restore()` 恢复登录态、下载列表、设置
## 设置settings.tsx / settingsStore
- `coverQuality: 'hd' | 'normal'`:控制封面图清晰度
- `hd`:原始 URL不加参数
- `normal`:追加 `@320w_180h_1c.webp` 缩略图参数,节省流量
- VideoCard 和 BigVideoCard 均从 `settingsStore` 读取此值
- 退出登录功能(调用 `authStore.logout()`
## imageUrl 工具
```ts
proxyImageUrl(url) // Web 端转本地代理 localhost:3001/bilibili-img/...(绕防盗链)
// Native 端将 http:// 强制改为 https://
coverImageUrl(url, quality) // 在 proxyImageUrl 基础上附加清晰度参数
```
## 主题色
- 主色:`#00AEEC`B 站蓝)
- 文字主色:`#212121`
- 次要文字:`#999`
- 背景:`#f4f4f4`(列表)/ `#fff`(卡片)
- 危险色(退出登录等):`#ff4757`
## 开发注意事项
### 运行方式
- Dev Build推荐`expo run:android`,支持 react-native-videoDASH/HLS
- Expo Go`expo start` 扫码react-native-video 可用但部分编解码受限,降级用 WebView 方案
### 常见问题
- **视频无法播放**:检查 `fnval`DASH 需确认 `buildDashMpdUri` 生成的 MPD 合法
- **API 403**`buvid3` Cookie 或 `SESSDATA` 失效,清除 AsyncStorage 重试
- **评论为空**B 站部分视频评论区关闭,`replies` 字段为 null 属正常
- **二维码过期**`pollQRCode` 返回 `code === 86038`,关闭 Modal 重新打开即可
- **直播无法播放**:确认 HLS URL 有效;部分直播间仅提供 FLV需选 FLV 流
- **封面加载慢**:在设置中切换为「普通」清晰度可减小图片体积
### 扩展方向
- 动态流:需更高权限 Cookie`bili_jct` CSRF Token
- 投稿/点赞/收藏:需登录态 + CSRF
- 离线弹幕导出DanmakuItem 已有完整数据结构