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
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
func setJobProgress(job *RecordJob, phase string, pct int) {
|
func setJobProgress(job *RecordJob, phase string, pct int) {
|
||||||
|
phase = strings.TrimSpace(phase)
|
||||||
|
phaseLower := strings.ToLower(phase)
|
||||||
|
|
||||||
|
// clamp pct 0..100
|
||||||
if pct < 0 {
|
if pct < 0 {
|
||||||
pct = 0
|
pct = 0
|
||||||
}
|
}
|
||||||
@ -10,30 +17,76 @@ func setJobProgress(job *RecordJob, phase string, pct int) {
|
|||||||
pct = 100
|
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()
|
jobsMu.Lock()
|
||||||
defer jobsMu.Unlock()
|
defer jobsMu.Unlock()
|
||||||
|
|
||||||
// ✅ Sobald Postwork/Phase läuft oder Aufnahme beendet ist:
|
// Sobald Postwork läuft oder Aufnahme beendet ist -> Recorder darf NICHTS mehr überschreiben.
|
||||||
// keine "alten" Recorder-Updates mehr akzeptieren
|
inPostwork := job.EndedAt != nil || (strings.TrimSpace(job.Phase) != "" && strings.ToLower(strings.TrimSpace(job.Phase)) != "recording")
|
||||||
if job.EndedAt != nil || (job.Phase != "" && job.Phase != "recording") {
|
if inPostwork {
|
||||||
// optional: trotzdem Phase aktualisieren, aber Progress nicht senken
|
// harte Blockade: alte recording-Updates dürfen weder Phase noch Progress anfassen
|
||||||
if phase != "" {
|
if phaseLower == "" || phaseLower == "recording" {
|
||||||
job.Phase = phase
|
return
|
||||||
}
|
}
|
||||||
if pct < job.Progress {
|
|
||||||
pct = job.Progress
|
|
||||||
}
|
|
||||||
job.Progress = pct
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recording-Phase:
|
// Phase aktualisieren (aber nur wenn nicht leer)
|
||||||
if phase != "" {
|
if phase != "" {
|
||||||
job.Phase = phase
|
job.Phase = phase
|
||||||
}
|
}
|
||||||
// ✅ niemals rückwärts
|
|
||||||
if pct < job.Progress {
|
// Progress-Logik:
|
||||||
pct = job.Progress
|
// - 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)
|
// ✅ Phase für Recording explizit setzen (damit spätere Progress-Writer das erkennen können)
|
||||||
jobsMu.Lock()
|
setJobProgress(job, "recording", 1)
|
||||||
job.Phase = "recording"
|
|
||||||
if job.Progress < 1 {
|
|
||||||
job.Progress = 1
|
|
||||||
}
|
|
||||||
jobsMu.Unlock()
|
|
||||||
notifyJobsChanged()
|
notifyJobsChanged()
|
||||||
|
|
||||||
// ---- Aufnahme starten (Output-Pfad sauber relativ zur EXE auflösen) ----
|
// ---- 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)
|
// beim Start: Queue-Status refresh (sollte jetzt "running" werden)
|
||||||
{
|
{
|
||||||
st := postWorkQ.StatusForKey(postKey)
|
st := postWorkQ.StatusForKey(postKey)
|
||||||
|
|
||||||
jobsMu.Lock()
|
jobsMu.Lock()
|
||||||
job.PostWork = &st
|
job.PostWork = &st
|
||||||
// optional: wenn du "queued" Progress optisch unterscheiden willst
|
|
||||||
if job.Phase == "postwork" && job.Progress < 71 {
|
|
||||||
job.Progress = 71
|
|
||||||
}
|
|
||||||
|
|
||||||
jobsMu.Unlock()
|
jobsMu.Unlock()
|
||||||
|
|
||||||
|
// optisches "queued" bumping
|
||||||
|
setJobProgress(job, "postwork", 71)
|
||||||
|
|
||||||
notifyJobsChanged()
|
notifyJobsChanged()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out := strings.TrimSpace(postOut)
|
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")
|
// Helper: Progress nur nach oben (gegen "rückwärts")
|
||||||
setPhase := func(phase string, pct int) {
|
setPhase := func(phase string, pct int) {
|
||||||
jobsMu.Lock()
|
// Phase+Progress inkl. Mapping/Monotonie
|
||||||
if pct < job.Progress {
|
setJobProgress(job, phase, pct)
|
||||||
pct = job.Progress
|
|
||||||
}
|
|
||||||
job.Phase = phase
|
|
||||||
job.Progress = pct
|
|
||||||
|
|
||||||
// Queue-Status auch bei Phase-Wechsel aktuell halten (nice für UI)
|
// Queue-Status aktuell halten
|
||||||
st := postWorkQ.StatusForKey(postKey)
|
st := postWorkQ.StatusForKey(postKey)
|
||||||
|
jobsMu.Lock()
|
||||||
job.PostWork = &st
|
job.PostWork = &st
|
||||||
|
|
||||||
jobsMu.Unlock()
|
jobsMu.Unlock()
|
||||||
|
|
||||||
notifyJobsChanged()
|
notifyJobsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
// backend\serve_video.go
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -104,9 +107,9 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
|||||||
|
|
||||||
mp4 := strings.TrimSuffix(path, filepath.Ext(path)) + ".mp4"
|
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
|
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()
|
inSize = fi.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,10 +121,8 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = 10
|
// Throttle + monoton (lokal), globale Monotonie macht setJobProgress
|
||||||
const span = 60 // 10..69 (70 startet "moving")
|
lastProgress := -1
|
||||||
|
|
||||||
lastProgress := base
|
|
||||||
lastTick := time.Now().Add(-time.Second)
|
lastTick := time.Now().Add(-time.Second)
|
||||||
|
|
||||||
onRatio := func(r float64) {
|
onRatio := func(r float64) {
|
||||||
@ -131,21 +132,30 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
|||||||
if r > 1 {
|
if r > 1 {
|
||||||
r = 1
|
r = 1
|
||||||
}
|
}
|
||||||
p := base + int(r*float64(span))
|
|
||||||
if p >= 70 {
|
p := int(math.Round(r * 100))
|
||||||
p = 69
|
if p < 0 {
|
||||||
|
p = 0
|
||||||
|
}
|
||||||
|
if p > 100 {
|
||||||
|
p = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nur steigen lassen
|
||||||
if p <= lastProgress {
|
if p <= lastProgress {
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastProgress = p
|
lastProgress = p
|
||||||
lastTick = time.Now()
|
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)
|
remuxCtx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
||||||
@ -155,10 +165,12 @@ func maybeRemuxTSForJob(job *RecordJob, path string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = os.Remove(path) // TS entfernen, wenn MP4 ok
|
_ = os.Remove(path) // TS entfernen, wenn MP4 ok
|
||||||
setJobPhase(job, "remuxing", 69) // ✅ Remux finished (nie rückwärts)
|
|
||||||
return mp4, nil
|
|
||||||
|
|
||||||
|
// ✅ Remux finished
|
||||||
|
setJobProgress(job, "remuxing", 100)
|
||||||
|
|
||||||
|
return mp4, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveToDoneDir(src string) (string, error) {
|
func moveToDoneDir(src string) (string, error) {
|
||||||
|
|||||||
@ -411,17 +411,20 @@ func buildFFmpegStreamArgs(inPath string, prof TranscodeProfile) []string {
|
|||||||
|
|
||||||
gop := "60"
|
gop := "60"
|
||||||
vf := fmt.Sprintf("scale=-2:%d", prof.Height)
|
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"
|
movflags := "frag_keyframe+empty_moov+default_base_moof"
|
||||||
|
|
||||||
return []string{
|
return []string{
|
||||||
"-hide_banner",
|
"-hide_banner",
|
||||||
"-loglevel", "error",
|
"-loglevel", "error",
|
||||||
|
"-nostdin",
|
||||||
"-y",
|
"-y",
|
||||||
"-i", inPath,
|
"-i", inPath,
|
||||||
|
|
||||||
|
// ✅ robust (wie im File-Transcode)
|
||||||
|
"-map", "0:v:0?",
|
||||||
|
"-map", "0:a:0?",
|
||||||
|
"-sn",
|
||||||
|
|
||||||
"-vf", vf,
|
"-vf", vf,
|
||||||
|
|
||||||
"-c:v", "libx264",
|
"-c:v", "libx264",
|
||||||
@ -440,7 +443,6 @@ func buildFFmpegStreamArgs(inPath string, prof TranscodeProfile) []string {
|
|||||||
|
|
||||||
"-movflags", movflags,
|
"-movflags", movflags,
|
||||||
|
|
||||||
// ✅ wichtig: Format explizit + Ausgabe in stdout
|
|
||||||
"-f", "mp4",
|
"-f", "mp4",
|
||||||
"pipe:1",
|
"pipe:1",
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user