修第三批 P2:除零、停止/暂停的响应与状态一致性、临时文件、带 BOM 的 ASS
一、弹幕滚动时间不再整数除零 write() 里拿 (PlayResX+length) 做除数,PlayResX 被配成 0 又收到一条空内容弹幕时 分母正好是 0,直接 panic 终止整场录制。抽成 scrollMarks 并给分母为 0 的兜底 (退回半个停留时长,至少让它正常显示出来)。 二、点停止不再要等整个块渲完 jobAttach 原来只看 paused:停止请求若落在 jobGate 之后、ffmpeg 启动过程中, job.proc 还没登记,jobStop 拿不到进程可杀,用户得等整个块渲完(大块要几分钟)。 现在 attach 时发现任务已请求停止就地把它杀掉,上层拿到 errJobStopped, 卡片显示的是「已停止」而不是「失败」。 三、暂停没挂住就回滚状态 jobPause 先把 job.paused 置 true 再调 suspendProcess,挂起失败时状态已经改了: 界面显示「已暂停」而 ffmpeg 还在跑。失败时回滚(只在 proc 仍是同一个进程时回滚)。 jobResume 同理:恢复不成放回「已暂停」,免得显示在跑却停着。 四、.ass 还原的临时 jsonl 用完就删 还原结果写不进录播目录时会退到系统临时目录的固定名文件,原来从不清理, 每失败一次留一份。新增 danmuSource.tempJsonl 标记 + dropTempDanmu 收尾删除; 只删标记过的临时文件 —— 正常情况下那个路径是用户录播目录里的同名 jsonl, 删了就是丢数据。 五、带 BOM 的 ASS 能整份解析 带 UTF-8 BOM 时首行是 BOM+[Script Info],一个节都进不去,最后只报 「可能不是弹幕文件」,跟真实原因完全对不上。解析时剥掉 BOM。 六、头像下载期间暂停/停止不用再干等 单次超时 30 秒 × 3 次重试,最坏 96 秒才响应暂停/停止。单次超时收到 10 秒 (头像只有几百 KB),并且每次重试前过闸门:暂停在这里等、停止直接放弃, 不再把剩下的重试跑完;停止也不再打「头像不可用」的告警。 验证: - gofmt -l 空;go vet ./... 空;go build 与 GOOS=linux go build 通过 - go test ./... 全绿(10.9s);go test -race ./... 全绿(14.5s) - 新增 p2_fixes_test.go 8 个用例;本轮 6 项加上一批 6 项共 12 项反向验证全部通过 (每项临时退回源码 → 对应测试确实失败 → 恢复) - 51897 隔离实例上完整复测:e2e.py 37 项全过、smoke_extra.py 33 项全过
This commit is contained in:
@@ -146,6 +146,18 @@ func textWidth(text string, fontSize int) int {
|
||||
return w
|
||||
}
|
||||
|
||||
// 弹幕滚动的两个关键时刻:完全进入(emerge)和完全离开(left)。
|
||||
// resX 是画面宽度、length 是弹幕宽度,两者相加是它要走的整段行程。
|
||||
// 配置异常(PlayResX 为 0)碰上空内容时这段行程会是 0,直接拿它做除数是整数除零 panic
|
||||
// (会终止整场录制),这种情况退回「半个停留时长」,至少让它正常显示出来。
|
||||
func scrollMarks(rel int64, resX, length int, show int64) (emerge, left int64) {
|
||||
total := int64(resX + length)
|
||||
if total <= 0 {
|
||||
return rel + show/2, rel + show
|
||||
}
|
||||
return rel + int64(length)*show/total, rel + int64(resX)*show/total
|
||||
}
|
||||
|
||||
// 在弹道数组里寻找空闲弹道,返回弹道行号,没有空闲返回 -1,公式与 acfundanmu 保持一致
|
||||
func placeLane(last []laneState, dt laneState, leftTime int64) int {
|
||||
for i, t := range last {
|
||||
@@ -242,12 +254,12 @@ func (w *danmuWriter) write(c *acfundanmu.Comment) {
|
||||
|
||||
// 原格式 ASS,碰撞避让和坐标公式与 acfundanmu 完全一致
|
||||
length := utf8.RuneCountInString(c.Content) * s.FontSize
|
||||
emergeO, leftO := scrollMarks(rel, s.PlayResX, length, danmuShowDur)
|
||||
dtO := laneState{
|
||||
appear: rel,
|
||||
emerge: rel + (int64(length)*danmuShowDur)/int64(s.PlayResX+length),
|
||||
emerge: emergeO,
|
||||
disappear: rel + danmuShowDur,
|
||||
}
|
||||
leftO := rel + (int64(s.PlayResX)*danmuShowDur)/int64(s.PlayResX+length)
|
||||
if i := placeLane(w.lastO, dtO, leftO); i >= 0 {
|
||||
if _, err := fmt.Fprintf(w.ass, dialogueTmpl,
|
||||
assTime(dtO.appear), assTime(dtO.disappear),
|
||||
@@ -262,12 +274,12 @@ func (w *danmuWriter) write(c *acfundanmu.Comment) {
|
||||
width := textWidth(text, s.FontSize)
|
||||
avW, avGap := avatarSize(s.FontSize), avatarGap(s.FontSize)
|
||||
total := avW + avGap + width // 头像、间隔和文字的总宽度
|
||||
emergeN, leftN := scrollMarks(rel, s.PlayResX, total, danmuShowDur)
|
||||
dtN := laneState{
|
||||
appear: rel,
|
||||
emerge: rel + (int64(total)*danmuShowDur)/int64(s.PlayResX+total),
|
||||
emerge: emergeN,
|
||||
disappear: rel + danmuShowDur,
|
||||
}
|
||||
leftN := rel + (int64(s.PlayResX)*danmuShowDur)/int64(s.PlayResX+total)
|
||||
if i := placeLane(w.lastN, dtN, leftN); i >= 0 {
|
||||
if _, err := fmt.Fprintf(w.nick, dialogueNickTmpl,
|
||||
assTime(dtN.appear), assTime(dtN.disappear),
|
||||
|
||||
Reference in New Issue
Block a user