feat(mobile): danmaku#205
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
SafeDep Report SummaryPackage Details
This report is generated by SafeDep Github App |
|
Caution Review failedThe pull request is closed. Walkthrough为移动端播放器新增弹幕功能:包含后端 proto 与二进制请求支持、Bilibili 分段弹幕加载、Skia 渲染引擎、设置 UI 与模态窗、配置/依赖更新及若干布局尺寸 hook 替换。 Changes
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)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 分钟 Possibly related PRs
诗歌
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorLine 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含义不明确。容器的
padding为10(左右共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 重命名为danmakuVisible或danmakuActive,以更准确地表达其含义,避免与用户偏好设置混淆。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 constapps/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:getBuffer与get/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, + } + }然后在
get、getBuffer、request中复用这些方法。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:isPlaying、enabled、fontMgr、width等普通值在useFrameCallback闭包中被捕获,依赖组件重渲染来更新。这些值不是
SharedValue,而是通过闭包捕获的。虽然 props 变化会触发重渲染从而更新回调,但在两次渲染之间这些值是过时的。特别是isPlaying如果变化频繁,可能在更新生效前多执行几帧。如果需要精确的帧级响应,建议将isPlaying和enabled改为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 配置以消除此警告。
* 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>
* 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>

Summary by CodeRabbit
新功能
改进
依赖更新
文档