Skip to content

feat(mobile): danmaku#205

Merged
roitium merged 5 commits into
devfrom
feat/danmaku
Feb 7, 2026
Merged

feat(mobile): danmaku#205
roitium merged 5 commits into
devfrom
feat/danmaku

Conversation

@roitium
Copy link
Copy Markdown
Collaborator

@roitium roitium commented Feb 7, 2026

Summary by CodeRabbit

  • 新功能

    • 在播放器页面添加弹幕显示及可配置的弹幕设置面板(开关、过滤等级滑块)。
    • 新增「歌词」设置页及相应设置项入口。
  • 改进

    • 改善窗口尺寸处理,提升布局与响应式适配。
    • 在播放界面集成弹幕展示控件并支持按需启用。
  • 依赖更新

    • 添加滑块组件库与 Protobuf 支持用于弹幕功能。
  • 文档

    • 在使用指南与 README 中补充弹幕说明与配置步骤。

@roitium roitium added the enhancement New feature or request label Feb 7, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bbplayer-docs Ready Ready Preview, Comment Feb 7, 2026 2:00pm

@roitium roitium marked this pull request as ready for review February 7, 2026 13:25
@safedep
Copy link
Copy Markdown

safedep Bot commented Feb 7, 2026

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

Package Details
Package Malware Vulnerability Risky License Report
icon @jsdoc/salty @ 0.2.9
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon @react-native-community/slider @ 5.1.2
pnpm-lock.yaml apps/mobile/package.json
ok icon
ok icon
ok icon
🔗
icon bluebird @ 3.7.2
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon catharsis @ 0.9.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon escodegen @ 1.14.3
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon espree @ 9.6.1
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon estraverse @ 4.3.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon expo-blur @ 55.0.4
pnpm-lock.yaml apps/mobile/package.json
ok icon
ok icon
ok icon
🔗
icon glob @ 8.1.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon js2xmlparser @ 4.0.2
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon jsdoc @ 4.0.5
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon klaw @ 3.0.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon levn @ 0.3.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon linkify-it @ 5.0.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon markdown-it @ 14.1.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon markdown-it-anchor @ 8.6.7
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon marked @ 4.3.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon mdurl @ 2.0.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon minimatch @ 5.1.6
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon optionator @ 0.8.3
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon prelude-ls @ 1.1.2
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon protobufjs @ 8.0.0
pnpm-lock.yaml apps/mobile/package.json
ok icon
ok icon
ok icon
🔗
icon protobufjs-cli @ 2.0.0
pnpm-lock.yaml apps/mobile/package.json
ok icon
ok icon
ok icon
🔗
icon punycode.js @ 2.3.1
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon requizzle @ 0.2.4
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon type-check @ 0.3.2
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon uc.micro @ 2.1.0
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon underscore @ 1.13.7
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗
icon xmlcreate @ 2.0.4
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗

This report is generated by SafeDep Github App

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 7, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

为移动端播放器新增弹幕功能:包含后端 proto 与二进制请求支持、Bilibili 分段弹幕加载、Skia 渲染引擎、设置 UI 与模态窗、配置/依赖更新及若干布局尺寸 hook 替换。

Changes

Cohort / File(s) Summary
配置与依赖
/.oxfmtrc.json, eslint.config.mjs, apps/mobile/package.json, apps/mobile/CHANGELOG.md
新增格式化/ESLint 忽略 **/dm.d.ts**/dm.js,升级/添加依赖(expo-blur、@react-native-community/slider、protobufjs、protobufjs-cli),更新版本与变更日志。
Bilibili API 与网络层
apps/mobile/src/lib/api/bilibili/proto/dm.proto, apps/mobile/src/lib/api/bilibili/api.ts, apps/mobile/src/lib/api/bilibili/client.ts, apps/mobile/src/types/apis/bilibili.ts
新增完整 DM protobuf 定义,新增 ApiClient.getBuffer() 返回二进制 ArrayBuffer,新增 BilibiliApi.getSegDanmaku() 解析分段弹幕并返回 BilibiliDanmakuItem
渲染引擎与加载器
apps/mobile/src/features/player/components/danmaku/DanmakuView.tsx, apps/mobile/src/features/player/hooks/danmaku/useDanmakuLoader.ts, apps/mobile/src/features/player/hooks/danmaku/useDanmakuRender.ts, apps/mobile/src/features/player/hooks/danmaku/constants.ts
新增 Skia 驱动的 DanmakuView 组件、分段加载器 hook 与逐帧渲染引擎 hook,及渲染常量配置。
UI 集成与设置
apps/mobile/src/app/player.tsx, apps/mobile/src/features/player/components/PlayerMainTab.tsx, apps/mobile/src/features/player/components/PlayerTrackInfo.tsx, apps/mobile/src/components/ModalRegistry.tsx, apps/mobile/src/components/modals/player/DanmakuSettingsModal.tsx
新增 danmakuEnabled 属性链路,TrackInfo 条件渲染 DanmakuView,注册并实现 Danmaku 设置模态窗。
应用状态与类型
apps/mobile/src/hooks/stores/useAppStore.ts, apps/mobile/src/types/core/appStore.ts, apps/mobile/src/types/navigation.ts, apps/mobile/src/hooks/analytics/useFeatureTracking.ts
在 app store 中新增 enableDanmakudanmakuFilterLevel,添加导航 modal 类型,埋点追踪设置变更。
弹幕工具与过滤
apps/mobile/src/utils/danmaku.ts
新增 cleanDanmaku():按权重过滤并可按秒限制弹幕数量,返回按进度排序结果。
设置页面与导航调整
apps/mobile/src/app/(tabs)/settings/playback.tsx, apps/mobile/src/app/(tabs)/settings/lyrics.tsx, apps/mobile/src/app/(tabs)/settings/_layout.tsx, apps/mobile/src/app/(tabs)/settings/index.tsx, apps/mobile/src/app/(tabs)/settings/appearance.tsx
在设置页添加/移动“歌词”分组,播放设置加入弹幕开关,移除旧歌词相关 UI。新增歌词设置页面。
窗口尺寸管理改动
apps/mobile/src/app/playlist/remote/toview.tsx, apps/mobile/src/features/home/SearchSuggestions.tsx, apps/mobile/src/features/player/components/PlayerLyrics.tsx, apps/mobile/src/features/player/components/lyrics/LyricsOffsetControl.tsx, packages/orpheus/example/src/components/SpectrumVisualizer.tsx
将多处 Dimensions.get('window') 替换为 useWindowDimensions(),调整布局/计算以支持动态尺寸。
其他小改动
packages/orpheus/android/src/main/java/.../OrpheusMusicService.kt, README.md, apps/docs/docs/guides/player.md
为两个方法添加 @OptIn(UnstableApi::class) 注解,更新 README 与文档增加弹幕说明。

Sequence Diagram(s)

sequenceDiagram
    participant Player as 播放器
    participant TrackInfo as PlayerTrackInfo
    participant DanmakuView as DanmakuView (Skia)
    participant Loader as useDanmakuLoader
    participant API as BilibiliApi
    participant Render as useDanmakuRender
    participant Canvas as Skia Canvas

    Player->>TrackInfo: 传入 danmakuEnabled, bvid, cid, currentTime
    TrackInfo->>DanmakuView: 挂载并传参
    DanmakuView->>Loader: useDanmakuLoader(bvid,cid,currentTime)
    Loader->>API: 可能请求 CID 或 getSegDanmaku(segment)
    API-->>Loader: 返回分段弹幕数据 (ArrayBuffer -> 解码)
    Loader->>DanmakuView: 更新 rawDataSV (SharedValue)
    DanmakuView->>Render: useDanmakuRender(rawDataSV,currentTime,...)
    loop 每帧
        Render->>Render: 生成/更新弹幕(轨道分配、位置)
        Render->>Canvas: 绘制到 Skia Picture
        Canvas-->>DanmakuView: 返回 Picture
    end
    DanmakuView->>Player: 在 UI 层渲染 Picture(pointerEvents none)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 分钟

Possibly related PRs

  • feat(mobile): danmaku #205 — 实现相同弹幕功能的 PR(DanmakuView、hooks、API、设置等),代码级高度相关。
  • feat: desktop lyrcis #122 — 在相同设置/播放页处理 desktop-lyrics 的变更,与本次移除/替换相关代码路径重叠。
  • feat: settings page #118 — 修改设置页面结构并触及相同设置导航点,与本次设置页面调整密切相关。

诗歌

🐰✨ 我是小兔在播放器旁,
弹幕飞舞像胡萝卜光,
分段加载排队忙,轨道间躲藏,
Skia 一画画满窗,
我跳着欢呼:弹幕已上线! 🎨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 提交标题清晰准确地总结了本次变更的主要内容:添加了弹幕功能到移动应用中。标题简洁、具体,采用了标准的特性提交格式(feat(mobile): danmaku),有效地传达了代码库的主要变更点。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/danmaku

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/mobile/src/features/player/components/PlayerLyrics.tsx (1)

89-89: ⚠️ Potential issue | 🟡 Minor

Line 89 的 extraData 空值检查是死代码。

Line 70 已经有相同的守卫检查,且 Line 71-79 已完成解构。执行到 Line 89 时 extraData 必定存在,此检查永远不会触发。

🧹 建议移除重复的守卫
 	if (item.isPaddingItem) {
 		return (
 			<Pressable
 				style={{ height: windowHeight / 2 }}
 				onPress={onPressBackground}
 			/>
 		)
 	}
-	if (!extraData) throw new Error('Extradata 不存在')
-
 	if (enableOldSchoolStyleLyric) {
🤖 Fix all issues with AI agents
In `@apps/mobile/src/features/player/components/danmaku/DanmakuView.tsx`:
- Line 83: The guard if (!picture) is invalid because picture is a SharedValue
returned by useDanmakuRender (initialized via createBlankPicture()) and is
always truthy; remove this dead check from DanmakuView and, if you actually need
to wait for readiness check picture.value instead (or rely on the default
created by createBlankPicture()). Ensure you update references to picture in the
component accordingly (use picture.value when testing readiness) and delete the
redundant if (!picture) branch.
- Around line 24-32: matchFamilyStyle(fontMgr.matchFamilyStyle) can return null
and passing that into customFontMgr.registerFont will crash; update the
DanmakuView initialization to null-check the result of
fontMgr.matchFamilyStyle(familyName, FontStyle.Bold) (the variable currently
named typeface), and if it's null obtain a safe fallback typeface (e.g., a
system default via fontMgr or skip registration) before calling
customFontMgr.registerFont; also consider logging or warning when falling back
so missing font cases are visible.
- Around line 64-81: The animated reaction currently uses resetEngine inside its
callback but the dependency array only includes position, which can lead to
stale closures; update the useAnimatedReaction dependency array to include
resetEngine (i.e., [position, resetEngine]) so the reaction sees the latest
function identity, or alternatively make resetEngine stable by wrapping it in
useCallback inside useDanmakuRender; ensure references to position, resetEngine,
loaderTime, and scheduleOnRN remain correct after the change.

In `@apps/mobile/src/features/player/components/PlayerTrackInfo.tsx`:
- Around line 207-215: The DanmakuView is rendered even when size.width is 0
causing a bad initial frame; update the conditional that renders DanmakuView in
PlayerTrackInfo (the block using currentTrack, enableDanmaku, danmakuEnabled) to
only render when size.width > 0 (e.g., add a guard like size.width > 0) so the
Skia canvas and danmaku positioning receive a valid width after onLayout; keep
using currentTrack.bilibiliMetadata.bvid/cid and the existing width/height
props.

In `@apps/mobile/src/features/player/hooks/danmaku/useDanmakuLoader.ts`:
- Around line 32-66: fetchSegment has early returns that leave
isLoadingRef.current stuck true and also accesses cidResult.value[0].cid without
checking the array; wrap the async body of fetchSegment in a try/finally so
isLoadingRef.current is always set back to false, and validate cidResult.value
is a non-empty array before using cidResult.value[0].cid (handle the empty case
by logging and returning inside the try, not before the finally), keeping the
initial guard (if (isLoadingRef.current) return) and the assignment
isLoadingRef.current = true at the top of fetchSegment.

In `@apps/mobile/src/features/player/hooks/danmaku/useDanmakuRender.ts`:
- Around line 184-187: The hex string produced from item.color via toString(16)
isn't zero-padded causing invalid CSS hex values; in useDanmakuRender (variables
strColor and color) convert item.color to a 6-character hex by left-padding with
zeros before passing into Skia.Color(`#${strColor}`) and keep the fallback to
Skia.Color(defaultColor) when item.color is null/undefined; ensure the padded
string uses uppercase/lowercase consistently and only add the leading '#' once.
- Around line 134-147: When height changes inside the useAnimatedReaction
callback you only reset tracks but not the running bullets; update the handler
in useDanmakuRender (the useAnimatedReaction block) to also clear activeBullets
and reset the cursor similar to resetEngine: set activeBullets to an empty array
(or clear its shared value) and reset cursor/index state so no old y positions
remain; ensure you perform these clears together with
tracks/staticTopTracks/staticBottomTracks to avoid stray bullets after
orientation/height changes.
- Around line 351-370: The frame-render callback in useFrameCallback
unconditionally creates a Skia.PictureRecorder and rebuilds picture.value every
frame even when danmaku is disabled or paused; add a guard at the start of this
useFrameCallback that checks the existing enabled and isPlaying flags (the same
booleans checked in the earlier useFrameCallback at Line 150) and return early
when !enabled || !isPlaying to avoid iterating activeBullets or calling
Skia.PictureRecorder(), ensuring picture.value is not rebuilt unnecessarily.
- Around line 259-282: The nested call to activeBullets.modify inside
staticTopTracks.modify (in the mode === 5 branch) violates Reanimated best
practices; change the logic so staticTopTracks.modify only computes and returns
the updated track timings and also records any new bullet items to add (e.g.,
capture the new bullet object(s) or their indices in a local array or variable),
then after staticTopTracks.modify completes perform a separate
activeBullets.modify call to push the recorded bullet(s) into activeBullets;
apply the same refactor for the mode === 4 branch so no modify calls are nested
and all SharedValue mutations happen in separate callbacks (refer to
staticTopTracks.modify and activeBullets.modify to locate the affected code).

In `@apps/mobile/src/lib/api/bilibili/api.ts`:
- Around line 171-176: The comment above the searchVideos function is
incorrect—remove or replace the leftover parameter list ("bvid: string, page:
number, options?") and update the JSDoc to match searchVideos' actual signature
(keyword, page, options?). Locate the comment block immediately preceding the
searchVideos function in api.ts (it likely references getSegDanmaku) and change
the param descriptions to reflect "keyword: string", "page: number", and the
optional "options?: { skipCookie?: boolean }" with appropriate brief
descriptions.

In `@apps/mobile/src/types/apis/bilibili.ts`:
- Line 497: The BilibiliDanmakuItem interface references the Long type but Long
is not imported; add an import for Long (e.g., import { Long } from "long" or
import { Long } from "protobufjs") at the top of the file and ensure the
declared type id: number | Long in BilibiliDanmakuItem remains valid under
strict TypeScript checking; update any related type exports/imports if needed to
keep consistency.
🧹 Nitpick comments (15)
packages/orpheus/example/src/components/SpectrumVisualizer.tsx (1)

84-89: 魔法数字 80 含义不明确。

容器的 padding10(左右共 20),但这里减去了 80。建议提取为命名常量或添加注释,说明这个值的计算依据(例如 padding + 额外边距等),以提高可读性。

♻️ 建议的改进
 const BAR_COUNT = 32
+const HORIZONTAL_INSET = 80 // container padding (20) + extra margin for visual spacing
-							width: (dimensions.width - 80) / BAR_COUNT,
+							width: (dimensions.width - HORIZONTAL_INSET) / BAR_COUNT,
apps/mobile/src/features/home/SearchSuggestions.tsx (1)

209-209: ItemSeparatorComponent 使用内联箭头函数会导致每次渲染都重新挂载分隔线组件。

建议将其提取为稳定引用,避免不必要的重新挂载。

♻️ 建议修改

在文件顶层或组件外部定义:

+const ItemSeparator = () => <Divider />

然后在 FlatList 中使用:

-				ItemSeparatorComponent={() => <Divider />}
+				ItemSeparatorComponent={ItemSeparator}
apps/mobile/src/app/player.tsx (1)

355-355: danmakuEnabled 语义可能引起混淆。

此处 danmakuEnabled={activeTab === 'main'} 实际控制的是"弹幕是否可见"(基于当前 tab),而非用户是否开启了弹幕功能(settings.enableDanmaku)。如果下游组件同时检查了 store 中的 enableDanmaku,建议将此 prop 重命名为 danmakuVisibledanmakuActive,以更准确地表达其含义,避免与用户偏好设置混淆。

apps/mobile/src/features/player/hooks/danmaku/constants.ts (1)

1-7: 建议添加 as const 以获得更精确的类型推导。

♻️ 建议的修改
-export const CONFIG = {
+export const CONFIG = {
 	SPEED: 0.15, // px per ms
 	SAFE_GAP: 4,
 	LINE_HEIGHT: 28,
 	FONT_SIZE: 16,
 	OPACITY: 0.8,
-}
+} as const
apps/mobile/src/types/core/appStore.ts (1)

13-14: 考虑为 danmakuFilterLevel 添加更具体的类型约束或文档注释。

danmakuFilterLevel 使用了宽泛的 number 类型,但根据弹幕权重范围 [0-10],建议添加注释说明其有效取值范围,或使用更精确的类型(如品牌类型或范围注释),方便后续维护者理解其语义。

apps/mobile/src/components/modals/player/DanmakuSettingsModal.tsx (1)

50-52: 硬编码颜色在深色模式下表现不佳。

maximumTrackTintColor='#000000' 在深色模式下几乎不可见,minimumTrackTintColor='#6200ee' 以及样式中的 '#e0e0e0''#666' 也未适配主题。建议使用 useTheme() 获取主题色。

♻️ 建议使用主题色替代硬编码颜色
+import { useTheme } from 'react-native-paper'
+
 const DanmakuSettingsModal = () => {
+	const { colors } = useTheme()
 	const close = useModalStore((state) => state.close)
 	// ...
 			<Slider
 				style={styles.slider}
 				minimumValue={0}
 				maximumValue={10}
 				step={1}
 				value={tempFilterLevel}
 				onValueChange={setTempFilterLevel}
 				onSlidingComplete={(value) =>
 					setSettings({ danmakuFilterLevel: value })
 				}
-				minimumTrackTintColor='#6200ee'
-				maximumTrackTintColor='#000000'
+				minimumTrackTintColor={colors.primary}
+				maximumTrackTintColor={colors.onSurfaceVariant}
 			/>
apps/mobile/src/utils/danmaku.ts (2)

15-29: 过滤后 progress 的类型未收窄,后续排序可能触发 TypeScript 严格模式警告。

filter 不会收窄数组元素的类型。Line 28 中 a.progress - b.progress 在类型层面 progress 仍是 number | undefined,可能导致 TS 编译警告。建议使用类型谓词(type predicate)或非空断言。

♻️ 使用类型谓词收窄类型
-	const filteredDanmakus = danmakus.filter((d) => {
-		const w = d.weight ?? 10
-		return w >= filterWeight && d.progress !== undefined && d.progress !== null
-	})
+	const filteredDanmakus = danmakus.filter(
+		(d): d is BilibiliDanmakuItem & { progress: number } => {
+			const w = d.weight ?? 10
+			return w >= filterWeight && d.progress !== undefined && d.progress !== null
+		},
+	)

26-30: sortedByWeight.sort(...) 是原地排序,此处返回的是同一个数组引用。

虽然 sortedByWeight 本身已是浅拷贝(Line 20 [...filteredDanmakus]),所以功能正确,但代码读起来像是在做两次不同的排序——实际上第二次排序覆盖了第一次的权重排序结果。这是预期行为(无 maxNumPerSecond 时不需要权重排序),可以加注释说明意图。

apps/mobile/src/lib/api/bilibili/client.ts (1)

133-197: getBufferget/request 存在大量重复代码。

URL 构建逻辑(Lines 140-151)与 get 方法(Lines 110-121)几乎完全一致,请求头/Cookie 组装逻辑(Lines 152-164)与 request 方法(Lines 29-41)也是重复的。建议提取公共的 URL 构建辅助方法和请求头组装方法以减少重复。

♻️ 提取公共方法减少重复
+	private buildUrl(
+		endpoint: string,
+		params?: Record<string, string | undefined> | string,
+	): string {
+		let url = endpoint
+		if (typeof params === 'string') {
+			url = `${endpoint}?${params}`
+		} else if (params) {
+			const searchParams = new URLSearchParams()
+			for (const [key, value] of Object.entries(params)) {
+				if (value !== undefined) {
+					searchParams.append(key, value)
+				}
+			}
+			url = `${endpoint}?${searchParams.toString()}`
+		}
+		return url
+	}
+
+	private buildHeaders(
+		extraHeaders?: Record<string, string>,
+		skipCookie?: boolean,
+	): Record<string, string> {
+		const cookieList = useAppStore.getState().bilibiliCookie
+		const cookie =
+			cookieList && !skipCookie ? serializeCookieObject(cookieList) : ''
+		return {
+			Cookie: cookie,
+			'User-Agent':
+				'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 BiliApp/6.66.0',
+			Referer: 'https://www.bilibili.com/',
+			Origin: 'https://www.bilibili.com',
+			...extraHeaders,
+		}
+	}

然后在 getgetBufferrequest 中复用这些方法。

apps/mobile/src/features/player/components/PlayerTrackInfo.tsx (1)

94-102: onLayout 触发的 setSize 每次布局变化都会引发重渲染。

如果布局尺寸不会频繁变化(通常不会),这不是大问题。但考虑到此组件在播放器中频繁渲染,可以考虑仅在尺寸实际变化时才 setState,避免不必要的重渲染。

♻️ 避免相同尺寸的重复 setState
 		<View
 			onLayout={(e) => {
 				const { width, height } = e.nativeEvent.layout
-				setSize({ width, height })
+				setSize((prev) => {
+					if (prev.width === width && prev.height === height) return prev
+					return { width, height }
+				})
 			}}
apps/mobile/src/features/player/hooks/danmaku/useDanmakuLoader.ts (1)

68-87: isLoadingRef 作为全局锁会导致预加载请求被静默丢弃且不会重试。

当前段正在加载时,checkAndLoad 中发起的预加载请求会因 isLoadingRef.current === true 被直接忽略(Line 34),且该段不会被标记为已加载,因此理论上下次检查时可以重试。但如果加载时间较长,用户可能在切段时出现弹幕空白。考虑使用 per-segment 的加载状态替代全局锁,或使用队列机制。

apps/mobile/src/features/player/hooks/danmaku/useDanmakuRender.ts (3)

196-239: 每帧为每个非黑色弹幕重复创建 8 个相同的 shadow 对象,建议提取为常量。

这 8 个 shadow 对象的值是固定的,但在帧回调的每次弹幕生成时都会重新分配。在高密度弹幕场景下,每帧可能生成多达 10 颗弹幕 × 8 个对象 = 80 次对象分配,增加 GC 压力。

♻️ 提取为模块级常量

在文件顶部(例如 CONFIG 导入之后)添加:

const BLACK_OUTLINE_SHADOWS = [
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: 1, y: 1 } },
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: -1, y: -1 } },
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: 1, y: -1 } },
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: -1, y: 1 } },
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: 1, y: 0 } },
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: -1, y: 0 } },
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: 0, y: 1 } },
	{ blurRadius: 0, color: Skia.Color('black'), offset: { x: 0, y: -1 } },
]

然后在帧回调中直接引用:

 			const shadows = isBlack
 				? undefined
-				: [
-						{
-							blurRadius: 0,
-							...
-						},
-					]
+				: BLACK_OUTLINE_SHADOWS

122-132: resetEngine 未被声明为 worklet,但直接访问了 rawDataSV.value 并调用 worklet 函数 binarySearch

当前实现在 JS 线程调用 .set().value 是可以工作的,但需要确认调用方是否可能从 UI 线程(如手势处理器)调用 resetEngine。如果是,则需要添加 'worklet' 指令。此外,resetEngine 函数没有被 useCallback 包裹,每次渲染都会创建新的引用,如果消费者将其作为依赖项使用,可能引发不必要的重渲染。


149-150: isPlayingenabledfontMgrwidth 等普通值在 useFrameCallback 闭包中被捕获,依赖组件重渲染来更新。

这些值不是 SharedValue,而是通过闭包捕获的。虽然 props 变化会触发重渲染从而更新回调,但在两次渲染之间这些值是过时的。特别是 isPlaying 如果变化频繁,可能在更新生效前多执行几帧。如果需要精确的帧级响应,建议将 isPlayingenabled 改为 SharedValue<boolean> 传入。

apps/mobile/src/lib/api/bilibili/proto/dm.proto (1)

1-19: Buf 静态分析报告:包路径与文件目录不匹配。

package bilibili.community.service.dm.v1 按 buf 规范应位于 bilibili/community/service/dm/v1/ 目录下,但实际位于 apps/mobile/src/lib/api/bilibili/proto/

如果这是从 Bilibili 平台引入的第三方 proto 定义且不打算通过 buf 管理,可以在 buf.yaml 中为此路径关闭 PACKAGE_DIRECTORY_MATCH 规则。否则,建议调整目录结构或 buf 配置以消除此警告。

Comment thread apps/mobile/src/features/player/components/danmaku/DanmakuView.tsx
Comment thread apps/mobile/src/features/player/components/danmaku/DanmakuView.tsx
Comment thread apps/mobile/src/features/player/components/danmaku/DanmakuView.tsx Outdated
Comment thread apps/mobile/src/features/player/components/PlayerTrackInfo.tsx Outdated
Comment thread apps/mobile/src/features/player/hooks/danmaku/useDanmakuLoader.ts
Comment thread apps/mobile/src/features/player/hooks/danmaku/useDanmakuRender.ts Outdated
Comment thread apps/mobile/src/features/player/hooks/danmaku/useDanmakuRender.ts
Comment thread apps/mobile/src/features/player/hooks/danmaku/useDanmakuRender.ts Outdated
Comment thread apps/mobile/src/lib/api/bilibili/api.ts
Comment thread apps/mobile/src/types/apis/bilibili.ts
@roitium roitium merged commit cc2fc6c into dev Feb 7, 2026
4 checks passed
roitium added a commit that referenced this pull request Feb 8, 2026
* fix(mobile): fix version code getter logic

* Merge pull request #193 from bbplayer-app/feat/nitro-fetch

* fix(orpheus, mobile): disable usesCleartextTraffic, only allow hdslb.com use http

* fix(mobile): disable r8

* fix(mobile): update proguard rules and enable R8

* chore(mobile): disable r8

* fix(mobile): r8 made @nandrorjo/galeria raise error

* feat: sync external playlist (#194)

* feat(mobile): e2e (#195)

* feat(mobile): 1 (#196)

* fix(mobile): add more current state check

I don't know why, but I hope it will work.

* feat(mobile): some improvements (#198)

* feat(orpheus): enhance backend retention capabilities

* fix(mobile): player page setpage

* fix(orpheus): allow androidx.media3.player wrapping in REPEAT_MODE_ONE (#199)

* fix(orpheus): allow androidx.media3.player wrapping in REPEAT_MODE_ONE

* chore(mobile): update changelog

---------

Co-authored-by: roitium <65794453+roitium@users.noreply.github.com>

* feat(mobile): improve playlist UI (#200)

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): verbatim lyrics (#201)

* feat(mobile, splash): improve verbatim lyrics

* perf(mobile): improve verbatim lyrics perf

* fix(mobile): player slider cannot slide

* fix(mobile): react compiler in PlayerLyrics

* chore(mobile): remove reanimated logger config

* chore(mobile): edit changelog

* fix(mobile): desktop lyrics not work

* feat(mobile): add firebase analytics integration (#203)

* fix(mobile): improve UI on small screen devices

* perf(mobile): some perf improvements

* feat(mobile): firebase

* feat(mobile): add firebase analytics track

* chore(mobile): update privacy

* chore(mobile): use firebase analytics new api

* chore(root): move some libraries

* fix(mobile): type

* chore(root, mobile): update package.json

* chore(docs): update docs

* chore(docs): update docs

* chore(docs): update docs

* chore(root): wiki

* chore(root): wiki

* chore(orpheus): wiki

* feat(mobile): danmaku (#205)

* feat(mobile): implement bilibili danmaku api

* fix(mobile): use `useWindowDimensions` instead of `Dimensions.get`

* feat(mobile): implement

* fix(mobile): 1

* chore(mobile): bump version

* chore(mobile): update changelog

* ci(mobile): wrong secret key

* fix(root): ci not work

* chore(root): just make sure

* chore(root): remove unnecessary easignore

* chore(root): remove unnecessary easignore

* chore(root): update update json

---------

Co-authored-by: Deyu Wang <kvtoDev@outlook.com>
roitium added a commit that referenced this pull request Feb 9, 2026
* fix(mobile): fix version code getter logic

* Merge pull request #193 from bbplayer-app/feat/nitro-fetch

* fix(orpheus, mobile): disable usesCleartextTraffic, only allow hdslb.com use http

* fix(mobile): disable r8

* fix(mobile): update proguard rules and enable R8

* chore(mobile): disable r8

* fix(mobile): r8 made @nandrorjo/galeria raise error

* feat: sync external playlist (#194)

* feat(mobile): e2e (#195)

* feat(mobile): 1 (#196)

* fix(mobile): add more current state check

I don't know why, but I hope it will work.

* feat(mobile): some improvements (#198)

* feat(orpheus): enhance backend retention capabilities

* fix(mobile): player page setpage

* fix(orpheus): allow androidx.media3.player wrapping in REPEAT_MODE_ONE (#199)

* fix(orpheus): allow androidx.media3.player wrapping in REPEAT_MODE_ONE

* chore(mobile): update changelog

---------

Co-authored-by: roitium <65794453+roitium@users.noreply.github.com>

* feat(mobile): improve playlist UI (#200)

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): 1

* feat(mobile): verbatim lyrics (#201)

* feat(mobile, splash): improve verbatim lyrics

* perf(mobile): improve verbatim lyrics perf

* fix(mobile): player slider cannot slide

* fix(mobile): react compiler in PlayerLyrics

* chore(mobile): remove reanimated logger config

* chore(mobile): edit changelog

* fix(mobile): desktop lyrics not work

* feat(mobile): add firebase analytics integration (#203)

* fix(mobile): improve UI on small screen devices

* perf(mobile): some perf improvements

* feat(mobile): firebase

* feat(mobile): add firebase analytics track

* chore(mobile): update privacy

* chore(mobile): use firebase analytics new api

* chore(root): move some libraries

* fix(mobile): type

* chore(root, mobile): update package.json

* chore(docs): update docs

* chore(docs): update docs

* chore(docs): update docs

* chore(root): wiki

* chore(root): wiki

* chore(orpheus): wiki

* feat(mobile): danmaku (#205)

* feat(mobile): implement bilibili danmaku api

* fix(mobile): use `useWindowDimensions` instead of `Dimensions.get`

* feat(mobile): implement

* fix(mobile): 1

* chore(mobile): bump version

* chore(mobile): update changelog

* ci(mobile): wrong secret key

* fix(root): ci not work

* chore(root): just make sure

* chore(root): remove unnecessary easignore

* chore(root): remove unnecessary easignore

* chore(root): update update json

* fix(mobile): auto compile protobuf when prepare

* chore(root): update app screenshots

* fix(mobile): player controls button not work (#208)

* fix(mobile): remove player page animation (#212)

* fix(mobile): remove player page animation

* chore(mobile): bump version

* fix(mobile): cover

* chore(mobile): Bump version to 2.3.1 and update release URL

Updated version and URL for the release notes.

---------

Co-authored-by: Deyu Wang <kvtoDev@outlook.com>
@coderabbitai coderabbitai Bot mentioned this pull request Feb 13, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Feb 24, 2026
@roitium roitium deleted the feat/danmaku branch February 28, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant