Skip to content

feat(danmaku): 支持自定义弹幕服务器 - #2716

Closed
qq164004640 wants to merge 1 commit into
Predidit:mainfrom
qq164004640:pr/custom-danmaku-server
Closed

qq164004640 wants to merge 1 commit into
Predidit:mainfrom
qq164004640:pr/custom-danmaku-server

Conversation

@qq164004640

Copy link
Copy Markdown

在设置中添加自定义弹幕服务器配置,播放页可选择该源加载弹幕【建议】

描述

【完全使用AI生成代码】

内置弹幕源(弹弹play)有时弹幕数量偏少,且曾遇到配额超限导致弹幕源不可用的情况。我自己部署过弹幕服务器,使用体验良好,但 Kazumi 目前只能选择内置弹幕源,无法使用自定义的弹幕服务器。

新增自定义弹幕服务器功能:

  • 设置中可配置自定义弹幕服务器地址(兼容弹弹play v2 接口,如 danmu_api / LogVar)
  • 播放时优先使用自定义服务器(按番剧标题自动匹配弹幕),未匹配到时自动回退到弹弹play,保证弹幕始终可用
  • 视频详情中展示弹幕来源,包含服务器实际匹配到的番剧与分集标题

由于没有上游的密钥所以镜像搜索和软件自带的弹幕功能是没法使用的,所以只有自定义弹幕服务器配置的弹幕能用

只有Windows 构建上实测通过:自定义弹幕加载、切换集数、与内置弹幕源的双开/单开组合均正常。
对代码一窍不通,增加的代码挺多的,不知道能不能采纳或者大佬构建一个更好的代码出来
image
image
image

关联的 Issues

#2652,#2660,#2661

PR 类型

  • Bug 修复
  • 添加新功能
  • 重构实现
  • 文档或格式
  • 其它

AI 辅助开发

  • 没有使用 AI 辅助
  • AI 辅助开发了部分功能,但是我已检查并理解 AI 所写的代码
  • 基本上都由 AI 实现,但是我非常了解 AI 的代码与写完的产物

AI 辅助模型:
Step-5-Preview

提交前检查

  • 我已经填写了 PR 模板中的所有内容
  • 我已经阅读了 贡献指引 ,并遵守指引进行开发
  • 这个 PR 的功能已经经过我的测试,并且我完全理解我(或 AI)做出的改动

附注

在设置中添加自定义弹幕服务器配置,播放页可选择该源加载弹幕;
修复自动匹配串集问题;新增弹幕来源展示。

/// 当前弹幕来源描述, 用于播放信息面板展示 (动漫名 + 集数 + 服务器)
@observable
String danmakuSourceLabel = '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: MobX generated code was not regenerated for the new observables/actions

danmakuSourceLabel, danmakuSourceAnimeTitle, danmakuSourceEpisodeTitle are annotated @observable, and setDanmakuSource / updateDanmakuSourceLabel / clearDanmakuSource are annotated @action, but player_danmaku_controller.g.dart only contains atoms for danmakuOn / danmakuLoading (only the getDanDanmakuByEpisodeID wrapper was hand-edited). These fields are therefore plain fields: the Observer in video_details_sheet.dart that reads danmakuSourceLabel will not rebuild when the label changes, and the actions are not wrapped. Re-run build_runner and commit the regenerated .g.dart.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return await _fetchDanDanmakuByBgmBangumiID(
bangumiId,
episode,
_applyDanmakuSourceLabel(result, title: title, episode: episode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Source label is written before the caller validates the load session

fetchDanmaku sets the label here, but VideoPageController only checks session.isActive && danmakuSession.isActive after this returns and drops stale results. A stale automatic load (previous episode, or one cancelled by a manual source switch via onBeforeApply) will still overwrite the current label with its own text (e.g. 弹幕获取失败 or a different episode), so the label no longer matches the loaded danmaku. Apply the label after the session check (e.g. in applyDanmakuLoad / applyUnavailableDanmakuLoad).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

// 自定义弹幕服务器的来源标签是平台名 (如 [bilibili]/[tencent]),
// 与弹弹play 关联源 ([BiliBili]/[Gamer]) 的过滤语义不同, 不套用这组开关,
// 避免关闭「弹弹play」来源时误过滤掉全部自定义服务器弹幕。
if (CustomDanmakuServerConfig.isEnabled) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Source filters are disabled for all danmaku whenever the custom server is enabled, including dandanplay fallback danmaku

The bypass is decided by the global config, not by where the current danmaku came from. When the custom server returns nothing, playback/local/manual paths fall back to dandanplay (withRelated=true), which yields [BiliBili]/[Gamer] entries — yet the user's BiliBili/Gamer/弹弹play toggles are now ignored. Track the actual server of the current load (e.g. DanmakuLoadResult.danmakuServer / the serverName passed to setDanmakuSource) and skip filtering only when it is the custom server.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

);
}
}
return _getCustomDanmakuByEpisodeOrder(title, episode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: Turning off the auto-match setting does not stop automatic fetching

The setting says 关闭后仅在播放页手动搜索选择弹幕, but when autoMatch is false the code only skips the match call and still falls through to _getCustomDanmakuByEpisodeOrder, which auto-picks the first search result and the N-th episode by position (an even less accurate guess). Playback and download prefetch will still auto-load custom danmaku. Return an empty result when !CustomDanmakuServerConfig.autoMatch.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

return (danmakus: <DanmakuEntry>[], animeTitle: null, episodeTitle: null);
}
if (CustomDanmakuServerConfig.autoMatch) {
final match = await CustomDanmakuClient.instance

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: An exception from match bypasses the documented episode-order fallback

The doc comment says match failure degrades to search → first anime → episode by order, but matchEpisodeId throws on network errors/timeouts/non-JSON/missing /match endpoint and this call is not wrapped, so the exception propagates and _getCustomDanmakuByEpisodeOrder is never tried. Either catch here or fix the comment.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}
final jsonData = await _get(
'$base${ApiEndpoints.customDanmuAPIComment}$episodeId',
queryParameters: {'format': 'json'},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: The Simplified/Traditional conversion setting is ignored for custom-server danmaku

The dandanplay path (DanmakuApi._getComments) sends chConvert from SettingsKeys.danmakuChConvert, but the custom-server comment request only sends format=json and no client-side conversion is applied. Users who chose 转为简体/繁体 get unconverted danmaku whenever the custom server is used. Forward chConvert here (the server is dandanplay-v2 compatible) or convert client-side.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (comment is Map<String, dynamic> &&
comment['p'] is String &&
comment['m'] is String)
DanmakuEntry.fromJson(comment),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: One malformed comment still aborts the whole list

The guard only checks types, but DanmakuEntry.fromJson does double.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]) and parts[3]. A p with fewer than 4 fields (source is optional in some implementations) throws RangeError, and non-integer values throw FormatException; the exception escapes the list literal so every comment for the episode is lost, contradicting the comment above. Wrap each fromJson in try/catch (or validate the split length / use tryParse).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

],
),
);
controller.dispose();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: TextEditingController disposed while the dialog's TextField is still mounted

KazumiDialog.show completes as soon as the route is popped, before the exit animation finishes. Disposing the controller here while the TextField (with autofocus) is still in the tree can trigger A TextEditingController was used after being disposed when it detaches/unfocuses. Move the dialog content into a StatefulWidget that owns and disposes the controller, or defer disposal until the route is fully removed.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}

Future<void> testCustomDanmakuServer() async {
if (!CustomDanmakuServerConfig.isEnabled) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: Connection test is blocked by the enable switch with a misleading message

isEnabled returns false whenever the enable switch is off, even if the URL is valid, so a user testing a valid address before enabling sees 请先填写有效的服务器地址. testConnection() already handles a null baseUrl; check CustomDanmakuServerConfig.baseUrl == null instead.

Suggested change
if (!CustomDanmakuServerConfig.isEnabled) {
if (CustomDanmakuServerConfig.baseUrl == null) {

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 9 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 7
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
lib/pages/player/controller/player_danmaku_controller.dart 130 New @observable/@action members not in regenerated .g.dart, so the label in the video details sheet doesn't update reactively
lib/pages/player/controller/player_danmaku_controller.dart 230 Source label is set before the session check, so stale loads overwrite the current label
lib/pages/player/player_item.dart 1112 Source filters are bypassed globally when the custom server is enabled, including for dandanplay fallback danmaku
lib/request/apis/danmaku_api.dart 142 With autoMatch off, danmaku is still auto-fetched through the episode-order fallback
lib/modules/danmaku/custom_danmaku_client.dart 64 chConvert (Simplified/Traditional) setting is ignored for custom-server comments
lib/modules/danmaku/custom_danmaku_client.dart 76 One malformed p field still throws in DanmakuEntry.fromJson and drops the whole list
lib/pages/settings/danmaku/danmaku_settings.dart 190 TextEditingController is disposed while the dialog's TextField is still mounted during the exit animation

SUGGESTION

File Line Issue
lib/request/apis/danmaku_api.dart 130 An exception from match skips the documented episode-order fallback
lib/pages/settings/danmaku/danmaku_settings.dart 209 Connection test is gated on the enable switch and shows a misleading "invalid address" message
Files Reviewed (13 files)
  • lib/modules/danmaku/custom_danmaku_client.dart - 2 issues
  • lib/modules/danmaku/custom_danmaku_server_config.dart - 0 issues
  • lib/pages/download/download_controller.dart - 0 issues
  • lib/pages/player/controller/player_danmaku_controller.dart - 2 issues
  • lib/pages/player/controller/player_danmaku_controller.g.dart - 0 issues (see the regeneration issue above)
  • lib/pages/player/danmaku_source_sheet.dart - 0 issues
  • lib/pages/player/player_item.dart - 1 issue
  • lib/pages/player/video_details_sheet.dart - 0 issues
  • lib/pages/settings/danmaku/danmaku_settings.dart - 2 issues
  • lib/pages/video/video_controller.dart - 0 issues
  • lib/request/apis/danmaku_api.dart - 2 issues
  • lib/request/config/api_endpoints.dart - 0 issues
  • lib/services/storage/settings_keys.dart - 0 issues

Fix these issues in Kilo Cloud


Reviewed by claude-opus-5.5 · Input: 276 · Output: 12K · Cached: 613.3K

@Predidit

Copy link
Copy Markdown
Owner

感谢你的工作,但是我们暂时不打算实现这个功能

@Predidit Predidit closed this Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants