档位改成「自动·第二高 + 实际档位」的下拉;手动合并也推送;头像缓存挪到程序目录
三件事: 1) 主播列表的录制档位 - 一律是下拉,不再有手填输入框。第一项「自动 · 第二高」(值 0)= 按这次读到的 档位排序自上往下取第二高(只有一档就用它,读不到档位表才退回最高档); 后面依次列出实际档位。选最高/最低/中间某一档直接点它,手动选过之后 「自动」仍留在列表里,随时能改回去。 - 语义变化:0 不再被写死成某个具体码率。原来记录档位时会把「没选过」的主播 按第二档定下来(写进 config.json),现在只在取流时解析,所以主播换了档位表 也能跟着走;app 启动时的补齐逻辑(applyCachedDefaultQualities)随之去掉。 - 解析规则统一到 resolveQualityBitrate:0 = 第二高,>0 = 不超过它的最高档, 都比它高时退回最低档;「自动」项会把解析到的档位名带出来 (自动 · 第二高(当前 = 蓝光 4M))。/api/status 和档位接口都多了 qualityBitrate。 - 点「档位」按钮时先给一句「正在读取码率档位…」:主播没开播时服务端要重试三次 才放弃,约 5 秒,之前这段时间界面没有任何反应。 2) 手动合并完成也推送(这就是「MeoW 只在测试时成功」的原因) - 原来只有自动合并调 sendNotify,手动合并从头到尾没推过,所以事件开着、渠道也通 却一直收不到。现在手动合并完成后同样按 mergeDone 事件推送两个渠道。 - 手动合并没有 uid,会拿成片名(再退到视频名)去对已有主播名,只有一个能对上时 才在文案里带上主播名,对不上就用「手动合并完成」。 3) 头像缓存挪到程序所在目录 - 原来存在「录播目录/avatar_cache」,换一次保存目录(甚至换盘)就要把同一批观众 的头像重新下一遍(一个头像可能 0.5MB)。现在统一放程序目录下的 avatar_cache。 - 旧位置作为只读回退:命中就搬过来(同盘改名,跨盘退回复制),不重复下载; 没被用到的旧文件留在原地,用到哪个搬哪个。 验证: - 新增 avatar_test.go 两个用例(缓存路径、旧位置搬家)和 notify_test.go 的 TestManualMergeSendsMergeDoneNotify(真跑一次合并,断言 webhook 与 MeoW 各收到 一条 mergeDone,且从文件名认出主播);qualities_test.go 改成新语义,新增 resolveQualityBitrate(7 种档位/码率组合)和 guessUIDByFileName。 - 端到端:把旧位置的头像缓存放在录播目录、头像地址指向连不通的端口,合并仍然成功 且画面里有那张头像(纯红像素 400 个),证明是搬来的而不是重新下载的;文件也从 录播目录搬到了程序目录。 - 浏览器:verify_streamer_br.py 重写成档位下拉的 20 项断言,verify_qualities.py 更新为新语义,两个都全过;verify_settings.py / verify_pick.py / verify_danmu_match.py 无回归;go test ./...、gofmt、go vet 干净。
This commit is contained in:
+71
-25
@@ -75,7 +75,8 @@ func TestSameQualities(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 记录档位时顺便把「还没设过码率」的主播按默认档位定下来
|
||||
// 记录档位时只缓存档位表,不动用户选的码率:
|
||||
// 0 表示「自动、按实际档位取第二高」,主播换了档位表要能跟着走
|
||||
func TestSetStreamerQualities(t *testing.T) {
|
||||
savedConfig := config
|
||||
savedStreamers := streamers.crt
|
||||
@@ -85,8 +86,8 @@ func TestSetStreamerQualities(t *testing.T) {
|
||||
}()
|
||||
|
||||
config.UIDs = uidList{
|
||||
{UID: 1, Danmu: true, Record: true, Bitrate: 0}, // 没设过 → 应被定成第二档
|
||||
{UID: 2, Danmu: true, Record: true, Bitrate: 1000}, // 手动设过 → 不动
|
||||
{UID: 1, Danmu: true, Record: true, Bitrate: 0}, // 自动
|
||||
{UID: 2, Danmu: true, Record: true, Bitrate: 1000}, // 手动指定过
|
||||
}
|
||||
streamers.crt = map[int]streamer{
|
||||
1: {UID: 1, Bitrate: 0},
|
||||
@@ -96,14 +97,17 @@ func TestSetStreamerQualities(t *testing.T) {
|
||||
if !setStreamerQualities(1, tiersForTest()) {
|
||||
t.Error("第一次记录档位应该算改动了配置")
|
||||
}
|
||||
if config.UIDs[0].Bitrate != 4000 {
|
||||
t.Errorf("uid 1 没设过码率,应被定成第二档 4000,实际 %d", config.UIDs[0].Bitrate)
|
||||
if config.UIDs[0].Bitrate != 0 {
|
||||
t.Errorf("自动的主播不该被写死成具体码率,实际 %d", config.UIDs[0].Bitrate)
|
||||
}
|
||||
if streamers.crt[1].Bitrate != 4000 || len(streamers.crt[1].Qualities) != 4 {
|
||||
t.Errorf("运行时的主播数据没同步:%+v", streamers.crt[1])
|
||||
if len(config.UIDs[0].Qualities) != 4 || len(streamers.crt[1].Qualities) != 4 {
|
||||
t.Errorf("档位没缓存到配置或运行时数据:%+v / %+v", config.UIDs[0], streamers.crt[1])
|
||||
}
|
||||
if streamers.crt[1].Bitrate != 0 {
|
||||
t.Errorf("运行时数据的码率不该被改动,实际 %d", streamers.crt[1].Bitrate)
|
||||
}
|
||||
|
||||
// 手动设过的不能被默认档位覆盖
|
||||
// 手动设过的同样不动
|
||||
setStreamerQualities(2, tiersForTest())
|
||||
if config.UIDs[1].Bitrate != 1000 {
|
||||
t.Errorf("uid 2 手动设过 1000,不该被改成 %d", config.UIDs[1].Bitrate)
|
||||
@@ -119,8 +123,43 @@ func TestSetStreamerQualities(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 启动时按缓存里的档位把默认码率补齐
|
||||
func TestApplyCachedDefaultQualities(t *testing.T) {
|
||||
// 实际用哪个档位:0 = 自动(第二高),填了具体码率 = 不超过它的最高档,
|
||||
// 都比它高时退回最低档
|
||||
func TestResolveQualityBitrate(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
bitrate int
|
||||
want int
|
||||
}{
|
||||
{"自动取第二高", 0, 4000},
|
||||
{"指定最高档", 8000, 8000},
|
||||
{"指定第二高", 4000, 4000},
|
||||
{"指定最低档", 1000, 1000},
|
||||
{"填了中间值取不超过它的最高档", 3000, 2000},
|
||||
{"比所有档位都高就取最高档", 99999, 8000},
|
||||
{"比所有档位都低就退回最低档", 200, 1000},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := resolveQualityBitrate(tiersForTest(), c.bitrate); got != c.want {
|
||||
t.Errorf("%s:应为 %d,实际 %d", c.name, c.want, got)
|
||||
}
|
||||
}
|
||||
// 没有档位表时保持原样(取流逻辑会退回最高档)
|
||||
if got := resolveQualityBitrate(nil, 0); got != 0 {
|
||||
t.Errorf("没有档位表时应返回 0,实际 %d", got)
|
||||
}
|
||||
if got := resolveQualityBitrate(nil, 2000); got != 2000 {
|
||||
t.Errorf("没有档位表时应原样返回,实际 %d", got)
|
||||
}
|
||||
// 只有一档时自动档就是它
|
||||
one := []qualityTier{{"高清", 2000}}
|
||||
if got := resolveQualityBitrate(one, 0); got != 2000 {
|
||||
t.Errorf("只有一档时自动应为 2000,实际 %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 手动合并的通知靠文件名猜主播:只有一个名字对得上才认
|
||||
func TestGuessUIDByFileName(t *testing.T) {
|
||||
savedConfig := config
|
||||
savedStreamers := streamers.crt
|
||||
defer func() {
|
||||
@@ -128,24 +167,31 @@ func TestApplyCachedDefaultQualities(t *testing.T) {
|
||||
streamers.crt = savedStreamers
|
||||
}()
|
||||
|
||||
config.UIDs = uidList{
|
||||
{UID: 1, Bitrate: 0, Qualities: tiersForTest()}, // 有缓存、没定过 → 补上
|
||||
{UID: 2, Bitrate: 2000, Qualities: tiersForTest()}, // 已经有值 → 不动
|
||||
{UID: 3, Bitrate: 0}, // 没缓存 → 不动
|
||||
config.UIDs = uidList{{UID: 1}, {UID: 2}, {UID: 3}}
|
||||
streamers.crt = map[int]streamer{
|
||||
1: {UID: 1, Name: "AC娘本体"},
|
||||
2: {UID: 2, Name: "随便唠会"},
|
||||
3: {UID: 3, Name: "阿"},
|
||||
}
|
||||
streamers.crt = map[int]streamer{1: {UID: 1}, 2: {UID: 2, Bitrate: 2000}, 3: {UID: 3}}
|
||||
|
||||
if !applyCachedDefaultQualities() {
|
||||
t.Fatal("有需要补的主播时应算改动")
|
||||
cases := []struct {
|
||||
file string
|
||||
want int
|
||||
}{
|
||||
{"2024-10-14 00-33-32 AC娘本体 嘿嘿 醒了_弹幕合并.mp4", 1},
|
||||
{"AC娘本体.mp4", 1},
|
||||
{"2024-10-14 00-33-32 随便唠会 摸鱼.mp4", 2},
|
||||
// 两个名字都在文件名里,猜不准就不猜
|
||||
{"AC娘本体 和 随便唠会 联动.mp4", 0},
|
||||
{"完全不认识的主播.mp4", 0},
|
||||
// 名字只有一个字的不参与匹配,太容易误伤
|
||||
{"阿 的录播.mp4", 0},
|
||||
{"", 0},
|
||||
}
|
||||
if config.UIDs[0].Bitrate != 4000 {
|
||||
t.Errorf("uid 1 应补成 4000,实际 %d", config.UIDs[0].Bitrate)
|
||||
}
|
||||
if config.UIDs[1].Bitrate != 2000 {
|
||||
t.Errorf("uid 2 不该被改动,实际 %d", config.UIDs[1].Bitrate)
|
||||
}
|
||||
if config.UIDs[2].Bitrate != 0 {
|
||||
t.Errorf("uid 3 没有档位缓存,不该被改动,实际 %d", config.UIDs[2].Bitrate)
|
||||
for _, c := range cases {
|
||||
if got := guessUIDByFileName(c.file); got != c.want {
|
||||
t.Errorf("%q 应猜出 %d,实际 %d", c.file, c.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user