updated
This commit is contained in:
parent
cb4ecfb889
commit
76ea79a1a9
Binary file not shown.
@ -1,8 +1,15 @@
|
||||
// backend\record_job_progress.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func setJobProgress(job *RecordJob, phase string, pct int) {
|
||||
phase = strings.TrimSpace(phase)
|
||||
phaseLower := strings.ToLower(phase)
|
||||
|
||||
// clamp pct 0..100
|
||||
if pct < 0 {
|
||||
pct = 0
|
||||
}
|
||||
@ -10,30 +17,76 @@ func setJobProgress(job *RecordJob, phase string, pct int) {
|
||||
pct = 100
|
||||
}
|
||||
|
||||
// "globale" Zielbereiche pro Phase (dein Pipeline-Modell)
|
||||
// postwork wartet: 70..72
|
||||
// remuxing: 72..78
|
||||
// moving: 78..84
|
||||
// probe: 84..86
|
||||
// assets: 86..99
|
||||
type rng struct{ start, end int }
|
||||
rangeFor := func(ph string) rng {
|
||||
switch ph {
|
||||
case "postwork":
|
||||
return rng{70, 72}
|
||||
case "remuxing":
|
||||
return rng{72, 78}
|
||||
case "moving":
|
||||
return rng{78, 84}
|
||||
case "probe":
|
||||
return rng{84, 86}
|
||||
case "assets":
|
||||
return rng{86, 99}
|
||||
default:
|
||||
return rng{0, 100}
|
||||
}
|
||||
}
|
||||
|
||||
jobsMu.Lock()
|
||||
defer jobsMu.Unlock()
|
||||
|
||||
// ✅ Sobald Postwork/Phase läuft oder Aufnahme beendet ist:
|
||||
// keine "alten" Recorder-Updates mehr akzeptieren
|
||||
if job.EndedAt != nil || (job.Phase != "" && job.Phase != "recording") {
|
||||
// optional: trotzdem Phase aktualisieren, aber Progress nicht senken
|
||||
if phase != "" {
|
||||
job.Phase = phase
|
||||
// Sobald Postwork läuft oder Aufnahme beendet ist -> Recorder darf NICHTS mehr überschreiben.
|
||||
inPostwork := job.EndedAt != nil || (strings.TrimSpace(job.Phase) != "" && strings.ToLower(strings.TrimSpace(job.Phase)) != "recording")
|
||||
if inPostwork {
|
||||
// harte Blockade: alte recording-Updates dürfen weder Phase noch Progress anfassen
|
||||
if phaseLower == "" || phaseLower == "recording" {
|
||||
return
|
||||
}
|
||||
if pct < job.Progress {
|
||||
pct = job.Progress
|
||||
}
|
||||
job.Progress = pct
|
||||
return
|
||||
}
|
||||
|
||||
// Recording-Phase:
|
||||
// Phase aktualisieren (aber nur wenn nicht leer)
|
||||
if phase != "" {
|
||||
job.Phase = phase
|
||||
}
|
||||
// ✅ niemals rückwärts
|
||||
if pct < job.Progress {
|
||||
pct = job.Progress
|
||||
|
||||
// Progress-Logik:
|
||||
// - wenn wir in Postwork sind und jemand phasenlokale 0..100 liefert (z.B. remuxing 25),
|
||||
// mappe das in den globalen Bereich der Phase.
|
||||
// - danach: niemals rückwärts.
|
||||
mapped := pct
|
||||
|
||||
if inPostwork {
|
||||
r := rangeFor(phaseLower)
|
||||
if r.start > 0 && r.end >= r.start {
|
||||
// Wenn pct kleiner ist als unser globaler Einstiegspunkt, interpretieren wir ihn als lokal (0..100)
|
||||
// und mappen in [start..end].
|
||||
if pct < r.start {
|
||||
width := float64(r.end - r.start)
|
||||
mapped = r.start + int(math.Round((float64(pct)/100.0)*width))
|
||||
} else {
|
||||
// Wenn schon "global" geliefert wird, trotzdem in den Bereich begrenzen
|
||||
if mapped < r.start {
|
||||
mapped = r.start
|
||||
}
|
||||
if mapped > r.end {
|
||||
mapped = r.end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
job.Progress = pct
|
||||
|
||||
// niemals rückwärts
|
||||
if mapped < job.Progress {
|
||||
mapped = job.Progress
|
||||
}
|
||||
job.Progress = mapped
|
||||
}
|
||||
|
||||
@ -107,12 +107,7 @@ func runJob(ctx context.Context, job *RecordJob, req RecordRequest) {
|
||||
}
|
||||
|
||||
// ✅ Phase für Recording explizit setzen (damit spätere Progress-Writer das erkennen können)
|
||||
jobsMu.Lock()
|
||||
job.Phase = "recording"
|
||||
if job.Progress < 1 {
|
||||
job.Progress = 1
|
||||
}
|
||||
jobsMu.Unlock()
|
||||
setJobProgress(job, "recording", 1)
|
||||
notifyJobsChanged()
|
||||
|
||||
// ---- Aufnahme starten (Output-Pfad sauber relativ zur EXE auflösen) ----
|
||||
@ -304,15 +299,16 @@ func runJob(ctx context.Context, job *RecordJob, req RecordRequest) {
|
||||
// beim Start: Queue-Status refresh (sollte jetzt "running" werden)
|
||||
{
|
||||
st := postWorkQ.StatusForKey(postKey)
|
||||
|
||||
jobsMu.Lock()
|
||||
job.PostWork = &st
|
||||
// optional: wenn du "queued" Progress optisch unterscheiden willst
|
||||
if job.Phase == "postwork" && job.Progress < 71 {
|
||||
job.Progress = 71
|
||||
}
|
||||
|
||||
jobsMu.Unlock()
|
||||
|
||||
// optisches "queued" bumping
|
||||
setJobProgress(job, "postwork", 71)
|
||||
|
||||
notifyJobsChanged()
|
||||
|
||||
}
|
||||
|
||||
out := strings.TrimSpace(postOut)
|
||||
@ -331,18 +327,15 @@ func runJob(ctx context.Context, job *RecordJob, req RecordRequest) {
|
||||
|
||||
// Helper: Progress nur nach oben (gegen "rückwärts")
|
||||
setPhase := func(phase string, pct int) {
|
||||
jobsMu.Lock()
|
||||
if pct < job.Progress {
|
||||
pct = job.Progress
|
||||
}
|
||||
job.Phase = phase
|
||||
job.Progress = pct
|
||||
// Phase+Progress inkl. Mapping/Monotonie
|
||||
setJobProgress(job, phase, pct)
|
||||
|
||||
// Queue-Status auch bei Phase-Wechsel aktuell halten (nice für UI)
|
||||
// Queue-Status aktuell halten
|
||||
st := postWorkQ.StatusForKey(postKey)
|
||||
jobsMu.Lock()
|
||||
job.PostWork = &st
|
||||
|
||||
jobsMu.Unlock()
|
||||
|
||||
notifyJobsChanged()
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
// backend\serve_video.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -104,9 +107,9 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
||||
|
||||
mp4 := strings.TrimSuffix(path, filepath.Ext(path)) + ".mp4"
|
||||
|
||||
// input size für fallback
|
||||
// input size für fallback (optional für progress/ffmpeg)
|
||||
var inSize int64
|
||||
if fi, err := os.Stat(path); err == nil && !fi.IsDir() {
|
||||
if fi, err := os.Stat(path); err == nil && fi != nil && !fi.IsDir() {
|
||||
inSize = fi.Size()
|
||||
}
|
||||
|
||||
@ -118,10 +121,8 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
||||
cancel()
|
||||
}
|
||||
|
||||
const base = 10
|
||||
const span = 60 // 10..69 (70 startet "moving")
|
||||
|
||||
lastProgress := base
|
||||
// Throttle + monoton (lokal), globale Monotonie macht setJobProgress
|
||||
lastProgress := -1
|
||||
lastTick := time.Now().Add(-time.Second)
|
||||
|
||||
onRatio := func(r float64) {
|
||||
@ -131,21 +132,30 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
||||
if r > 1 {
|
||||
r = 1
|
||||
}
|
||||
p := base + int(r*float64(span))
|
||||
if p >= 70 {
|
||||
p = 69
|
||||
|
||||
p := int(math.Round(r * 100))
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
if p > 100 {
|
||||
p = 100
|
||||
}
|
||||
|
||||
// nur steigen lassen
|
||||
if p <= lastProgress {
|
||||
return
|
||||
}
|
||||
// leicht throttlen
|
||||
if time.Since(lastTick) < 150*time.Millisecond && p < 79 {
|
||||
|
||||
// leicht throttlen (außer kurz vor Schluss)
|
||||
if time.Since(lastTick) < 150*time.Millisecond && p < 99 {
|
||||
return
|
||||
}
|
||||
|
||||
lastProgress = p
|
||||
lastTick = time.Now()
|
||||
setJobPhase(job, "remuxing", p)
|
||||
|
||||
// ✅ wichtig: 0..100 übergeben (Mapping macht setJobProgress)
|
||||
setJobProgress(job, "remuxing", p)
|
||||
}
|
||||
|
||||
remuxCtx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||
@ -155,10 +165,12 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_ = os.Remove(path) // TS entfernen, wenn MP4 ok
|
||||
setJobPhase(job, "remuxing", 69) // ✅ Remux finished (nie rückwärts)
|
||||
return mp4, nil
|
||||
_ = os.Remove(path) // TS entfernen, wenn MP4 ok
|
||||
|
||||
// ✅ Remux finished
|
||||
setJobProgress(job, "remuxing", 100)
|
||||
|
||||
return mp4, nil
|
||||
}
|
||||
|
||||
func moveToDoneDir(src string) (string, error) {
|
||||
|
||||
@ -411,17 +411,20 @@ func buildFFmpegStreamArgs(inPath string, prof TranscodeProfile) []string {
|
||||
|
||||
gop := "60"
|
||||
vf := fmt.Sprintf("scale=-2:%d", prof.Height)
|
||||
|
||||
// ✅ Fragmented MP4: spielbar bevor der File “fertig” ist
|
||||
// empty_moov + moof fragments => Browser kann früh starten
|
||||
movflags := "frag_keyframe+empty_moov+default_base_moof"
|
||||
|
||||
return []string{
|
||||
"-hide_banner",
|
||||
"-loglevel", "error",
|
||||
"-nostdin",
|
||||
"-y",
|
||||
"-i", inPath,
|
||||
|
||||
// ✅ robust (wie im File-Transcode)
|
||||
"-map", "0:v:0?",
|
||||
"-map", "0:a:0?",
|
||||
"-sn",
|
||||
|
||||
"-vf", vf,
|
||||
|
||||
"-c:v", "libx264",
|
||||
@ -440,7 +443,6 @@ func buildFFmpegStreamArgs(inPath string, prof TranscodeProfile) []string {
|
||||
|
||||
"-movflags", movflags,
|
||||
|
||||
// ✅ wichtig: Format explizit + Ausgabe in stdout
|
||||
"-f", "mp4",
|
||||
"pipe:1",
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user