档位改成「自动·第二高 + 实际档位」的下拉;手动合并也推送;头像缓存挪到程序目录
三件事: 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:
@@ -30,7 +30,7 @@ type streamer struct {
|
||||
Name string `json:"name"` // 主播名字
|
||||
Danmu bool `json:"-"` // 是否录制直播弹幕
|
||||
Record bool `json:"-"` // 是否录制直播视频
|
||||
Bitrate int `json:"-"` // 录制的直播源最高码率(Kbps),0 表示还没定(按实际档位取第二高)
|
||||
Bitrate int `json:"-"` // 选定的档位码率(Kbps),0 = 自动(按实际档位取第二高)
|
||||
Qualities []qualityTier `json:"-"` // 上次探测到的码率档位,没开播时页面靠它显示下拉
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ type uidSetting struct {
|
||||
UID int `json:"uid"`
|
||||
Danmu bool `json:"danmu"`
|
||||
Record bool `json:"record"`
|
||||
Bitrate int `json:"bitrate"` // 录制的直播源最高码率(Kbps),0 表示还没定(按实际档位取第二高)
|
||||
Bitrate int `json:"bitrate"` // 选定的档位码率(Kbps),0 = 自动(按实际档位取第二高)
|
||||
Qualities []qualityTier `json:"qualities,omitempty"` // 上次探测到的档位,缓存下来让没开播时也能在页面上选
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ type configData struct {
|
||||
MergeBitrate int `json:"mergeBitrate"` // 合并成片的目标码率(Kbps),0 表示使用默认质量
|
||||
DanmuFontSize int `json:"danmuFontSize"` // 合并时弹幕文字的字号(头像随字号等比缩放),0 表示跟随弹幕文件
|
||||
|
||||
DefaultBitrate int `json:"defaultBitrate"` // 新添加主播的默认下载码率上限(Kbps),0 表示录制码率最高的直播源
|
||||
DefaultBitrate int `json:"defaultBitrate"` // 新添加主播默认选的档位码率(Kbps),0 = 自动(按实际档位取第二高)
|
||||
|
||||
WebExternal bool `json:"webExternal"` // 是否允许外网访问 web UI,false 时只监听本机
|
||||
|
||||
@@ -508,8 +508,8 @@ func sameQualities(a, b []qualityTier) bool {
|
||||
}
|
||||
|
||||
// 记下某个主播当前的码率档位(离线时页面靠它显示下拉)。
|
||||
// 顺手把「还没设过码率」的主播按默认档位(第二高)定下来 —— 这样默认不会一直吃满最高档。
|
||||
// 返回是否改动了配置,调用方决定要不要 saveConfig。
|
||||
// 不动用户选的码率:0 表示「自动、按实际档位取第二高」,只在真正取流时解析,
|
||||
// 这样主播换了档位表也能跟着走,而不是把某个码率钉死。返回是否改动了配置。
|
||||
func setStreamerQualities(uid int, tiers []qualityTier) bool {
|
||||
if uid <= 0 || len(tiers) == 0 {
|
||||
return false
|
||||
@@ -524,18 +524,8 @@ func setStreamerQualities(uid int, tiers []qualityTier) bool {
|
||||
config.UIDs[i].Qualities = tiers
|
||||
changed = true
|
||||
}
|
||||
if config.UIDs[i].Bitrate == 0 {
|
||||
if def := defaultQualityBitrate(tiers); def > 0 {
|
||||
config.UIDs[i].Bitrate = def
|
||||
changed = true
|
||||
lPrintf("uid %d 还没设过下载码率,按当前直播档位默认取第二档:%s(%d Kbps)",
|
||||
uid, qualityNameFor(tiers, def), def)
|
||||
}
|
||||
}
|
||||
if s, ok := streamers.crt[uid]; ok {
|
||||
s.Qualities = config.UIDs[i].Qualities
|
||||
// 档位定下来后正在录的那场不打断,配置值从下次录制开始生效
|
||||
s.Bitrate = config.UIDs[i].Bitrate
|
||||
streamers.crt[uid] = s
|
||||
}
|
||||
break
|
||||
@@ -544,17 +534,44 @@ func setStreamerQualities(uid int, tiers []qualityTier) bool {
|
||||
return changed
|
||||
}
|
||||
|
||||
// 启动时把「缓存过档位、但还没定过码率」的主播按默认档位定下来
|
||||
func applyCachedDefaultQualities() bool {
|
||||
changed := false
|
||||
for _, u := range append(uidList(nil), config.UIDs...) {
|
||||
if u.Bitrate == 0 && len(u.Qualities) > 0 {
|
||||
if setStreamerQualities(u.UID, u.Qualities) {
|
||||
changed = true
|
||||
}
|
||||
// 按选定的码率决定实际用哪个档位:
|
||||
// 0(自动)取自上往下第二档(只有一个档位就用它);填了具体码率就取不超过它的最高档,
|
||||
// 都比它高时退回最低档。档位列表为空返回 0,交给调用方按老规矩取最高档。
|
||||
func resolveQualityBitrate(tiers []qualityTier, bitrate int) int {
|
||||
desc := sortQualitiesDesc(tiers)
|
||||
if len(desc) == 0 {
|
||||
return bitrate
|
||||
}
|
||||
if bitrate <= 0 {
|
||||
return defaultQualityBitrate(desc)
|
||||
}
|
||||
for _, t := range desc {
|
||||
if t.Bitrate <= bitrate {
|
||||
return t.Bitrate
|
||||
}
|
||||
}
|
||||
return changed
|
||||
return desc[len(desc)-1].Bitrate
|
||||
}
|
||||
|
||||
// 从文件(成片或录播)名里猜是哪个主播:名字出现在文件名里、且只有一个能对上时才认,
|
||||
// 认不出来返回 0。手动合并的推送靠它带上主播名,猜不准就退回通用文案。
|
||||
func guessUIDByFileName(name string) int {
|
||||
if name == "" {
|
||||
return 0
|
||||
}
|
||||
base := filepath.Base(name)
|
||||
matched := 0
|
||||
for _, s := range append(uidList(nil), config.UIDs...) {
|
||||
n := nameForLog(s.UID)
|
||||
if len([]rune(n)) < 2 || !strings.Contains(base, n) {
|
||||
continue
|
||||
}
|
||||
if matched != 0 {
|
||||
return 0 // 多个主播都对得上,不猜了
|
||||
}
|
||||
matched = s.UID
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// 保存 config.json,写入串行化并原子替换,避免并发写坏文件或读到写了一半的内容
|
||||
|
||||
Reference in New Issue
Block a user