录制档位改成「最高/第二高/最低/自定义」四种相对偏好,没开播也能先设

上一版的下拉里列的是「这次读到的实际档位」,没读到档位表(没开播)时下拉里就只剩
一项,想预先设「我要最高档」或「最低档」都做不到。改成按「实际档位里的第几档」来选:

- 新增 `qualityPref`:top(最高档)/ second(第二高,默认)/ lowest(最低档)/
  custom(取不超过给定码率的最高档,都比它高时退回最低档)。四种偏好任何时候都
  在列表里,开播后自动对应到实际档位;读到档位表后把对应结果标在选项后面
  (`第二高(蓝光 4M)`、`自定义(≤2500K = 高清)`)。
- `bitrate` 只在 custom 时有意义。`resolveQualityBitrate(tiers, pref, custom)` 统一
  解析:相对偏好没有档位表时返回 0(取流退回最高档),custom 返回填的上限。
  解析只在取流时做,所以主播换了档位表,偏好会跟着一起变。
- 老配置兼容:没有 qualityPref 时,`bitrate > 0` 当成 custom(就是原来的「码率上限」,
  行为不变),`bitrate == 0` 当成默认的第二高;在 checkConfig 里补齐(不写盘,下次
  保存自然带上)。`setBitrate` 相应换成 `setQuality(uid, pref, custom)`。
- 页面:选 custom 时旁边出现码率框,初始值就是**当前实际在用的那一档**(不会一切过去
  就掉到最低档),回车或失焦即提交,填错给提示并回填服务端的值;从 custom 切到别的
  档位不带 bitrate,服务端保留原值,改回来还是原来那个数。
- 设置区同步:新主播的默认档位也用同一套四项(`defaultQualityPref`),只有选自定义
  才露出码率上限框(切过去会补一个能用的 3000,免得新主播默默用最低档)。

顺带修一个脚本抓到的真 bug:档位名传成了档位对象(`esc()` 里会 `.replace`),只要
档位表非空就会抛异常,而 refresh() 整段包在 try/catch 里 —— 表现是整个主播列表
变成空白且没有任何报错。现在取的是 `qs[i].name`。

测试:qualities_test.go 重写 TestResolveQualityBitrate(10 种偏好/码率组合)并新增
TestMigrateQualityPref、TestUpgradeQualityPrefs、TestSetQuality;verify_streamer_br.py
改成带迷你后端的假服务(POST 会真正落到假状态上),30 项断言全过;verify_qualities.py
25 项(含设置区与 config.json 往返)全过;verify_settings.py / verify_pick.py /
verify_danmu_match.py 无回归;go test ./...、gofmt、go vet 干净。
This commit is contained in:
tamakyi
2026-09-25 20:42:22 +08:00
parent 7b853c1a22
commit 237fffa8fe
6 changed files with 549 additions and 215 deletions
+6 -6
View File
@@ -121,9 +121,9 @@ func fetchStreamQualities(uid int) ([]qualityTier, error) {
return streamTiers(info.StreamList), nil
}
// 获取 uid 的直播源(flv)。bitrate 是页面上选的档位码率,0 表示自动:
// 按这次拿到的实际档位自上往下取第二高(只有一个档位就用它,拿不到档位表退回最高档)
func getStreamURL(uid, bitrate int) (url string, quality string, err error) {
// 获取 uid 的直播源(flv)。pref/custom 是页面上选的档位偏好(最高/第二高/最低/自定义),
// 按这次拿到的实际档位对应上去;拿不到档位表时退回最高档
func getStreamURL(uid int, pref string, custom int) (url string, quality string, err error) {
sInfo, err := newStreamInfo(uid)
if err != nil {
return "", "", err
@@ -131,14 +131,14 @@ func getStreamURL(uid, bitrate int) (url string, quality string, err error) {
if sInfo == nil || len(sInfo.StreamList) == 0 {
return "", "", fmt.Errorf("uid 为%d的主播没有可用的直播源", uid)
}
// 顺手把码率档位记下来:页面上要用它做下拉,没开播时靠缓存显示
// 顺手把码率档位记下来:页面上要用它显示「这一档现在对应哪个档位名」,没开播时靠缓存显示
var tiers []qualityTier
if tiers = streamTiers(sInfo.StreamList); len(tiers) > 0 {
if setStreamerQualities(uid, tiers) {
saveConfig()
}
}
want := resolveQualityBitrate(tiers, bitrate)
want := resolveQualityBitrate(tiers, pref, custom)
// StreamList 按码率升序排列,从高往低找选中的那一档
st := sInfo.StreamList[len(sInfo.StreamList)-1]
if want > 0 {
@@ -262,7 +262,7 @@ func (s streamer) recordLive(ctx context.Context, liveID, filename string) {
var resumeFails int // 连续取不到直播源的次数
for {
url, quality, err := getStreamURL(s.UID, s.Bitrate)
url, quality, err := getStreamURL(s.UID, s.QualityPref, s.Bitrate)
if err != nil {
// 断流重连时直播源往往要过几秒才回来,窗口内多试几次,别把一场直播切成两段
if resumeFails < maxResumeRetry && ctx.Err() == nil && s.waitResume(ctx) {