Harden relay preview mp4 handling
这个提交包含在:
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -410,6 +411,186 @@ func TestProcessRelayPreviewPublishesBufferedWebM(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSegmentUploadPersistsRelayMP4InitSegment(t *testing.T) {
|
||||
store, err := newSessionStore(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("newSessionStore: %v", err)
|
||||
}
|
||||
|
||||
server := newMediaServer(store)
|
||||
session, err := store.createSession(CreateSessionRequest{UserID: "1", Title: "Relay MP4", Purpose: "relay", RelayBufferSeconds: 120})
|
||||
if err != nil {
|
||||
t.Fatalf("createSession: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/media/sessions/"+session.ID+"/segments?sequence=1&durationMs=10000", strings.NewReader("mp4-init"))
|
||||
req.Header.Set("Content-Type", "video/mp4;codecs=avc1")
|
||||
res := httptest.NewRecorder()
|
||||
server.routes().ServeHTTP(res, req)
|
||||
if res.Code != http.StatusAccepted {
|
||||
t.Fatalf("expected segment upload 202, got %d", res.Code)
|
||||
}
|
||||
|
||||
current, err := store.getSession(session.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("getSession: %v", err)
|
||||
}
|
||||
if current.RelayInitFilename != "000001.mp4" {
|
||||
t.Fatalf("expected relay init filename to be recorded, got %q", current.RelayInitFilename)
|
||||
}
|
||||
body, err := os.ReadFile(store.relayInitPath(session.ID))
|
||||
if err != nil {
|
||||
t.Fatalf("read relay init: %v", err)
|
||||
}
|
||||
if string(body) != "mp4-init" {
|
||||
t.Fatalf("unexpected relay init contents: %q", string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessRelayPreviewUsesPersistedInitForMP4Fragments(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
store, err := newSessionStore(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("newSessionStore: %v", err)
|
||||
}
|
||||
|
||||
session, err := store.createSession(CreateSessionRequest{UserID: "1", Title: "Relay MP4 Preview", Purpose: "relay", RelayBufferSeconds: 120})
|
||||
if err != nil {
|
||||
t.Fatalf("createSession: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(store.relayInitPath(session.ID), []byte(strings.Repeat("i", 6000)), 0o644); err != nil {
|
||||
t.Fatalf("write relay init: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000082.mp4"), []byte(strings.Repeat("a", 6000)), 0o644); err != nil {
|
||||
t.Fatalf("write segment 82: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000083.mp4"), []byte(strings.Repeat("b", 6000)), 0o644); err != nil {
|
||||
t.Fatalf("write segment 83: %v", err)
|
||||
}
|
||||
|
||||
if _, err := store.updateSession(session.ID, func(current *Session) error {
|
||||
current.Purpose = PurposeRelay
|
||||
current.RelayInitFilename = "000001.mp4"
|
||||
current.Segments = []SegmentMeta{
|
||||
{
|
||||
Sequence: 82,
|
||||
Filename: "000082.mp4",
|
||||
DurationMS: 10000,
|
||||
SizeBytes: 6000,
|
||||
ContentType: "video/mp4;codecs=avc1",
|
||||
},
|
||||
{
|
||||
Sequence: 83,
|
||||
Filename: "000083.mp4",
|
||||
DurationMS: 10000,
|
||||
SizeBytes: 6000,
|
||||
ContentType: "video/mp4;codecs=avc1",
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("updateSession: %v", err)
|
||||
}
|
||||
|
||||
fakeFFmpeg := filepath.Join(tempDir, "ffmpeg")
|
||||
script := "#!/bin/sh\ninput=''\noutput=''\nprev=''\nfor arg in \"$@\"; do\n if [ \"$prev\" = '-i' ]; then input=\"$arg\"; fi\n prev=\"$arg\"\n output=\"$arg\"\ndone\nif [ -n \"$input\" ] && [ -f \"$input\" ]; then cp \"$input\" \"$output\"; else : > \"$output\"; fi\n"
|
||||
if err := os.WriteFile(fakeFFmpeg, []byte(script), 0o755); err != nil {
|
||||
t.Fatalf("write fake ffmpeg: %v", err)
|
||||
}
|
||||
t.Setenv("PATH", tempDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
|
||||
if err := processRollingPreview(store, session.ID); err != nil {
|
||||
t.Fatalf("processRollingPreview: %v", err)
|
||||
}
|
||||
|
||||
current, err := store.getSession(session.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("getSession: %v", err)
|
||||
}
|
||||
if current.PreviewStatus != PreviewReady {
|
||||
t.Fatalf("expected preview ready, got %s", current.PreviewStatus)
|
||||
}
|
||||
if current.Playback.PreviewURL == "" {
|
||||
t.Fatalf("expected preview url to be populated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessRelayPreviewKeepsPreviousPreviewOnFailure(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
store, err := newSessionStore(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("newSessionStore: %v", err)
|
||||
}
|
||||
|
||||
session, err := store.createSession(CreateSessionRequest{UserID: "1", Title: "Relay Existing Preview", Purpose: "relay", RelayBufferSeconds: 120})
|
||||
if err != nil {
|
||||
t.Fatalf("createSession: %v", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(store.publicDir(session.ID), 0o755); err != nil {
|
||||
t.Fatalf("mkdir public dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(store.publicDir(session.ID), "preview.webm"), []byte("existing-preview"), 0o644); err != nil {
|
||||
t.Fatalf("write preview: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000001.webm"), []byte("segment-one"), 0o644); err != nil {
|
||||
t.Fatalf("write segment 1: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000002.webm"), []byte("segment-two"), 0o644); err != nil {
|
||||
t.Fatalf("write segment 2: %v", err)
|
||||
}
|
||||
if _, err := store.updateSession(session.ID, func(current *Session) error {
|
||||
current.Purpose = PurposeRelay
|
||||
current.PreviewStatus = PreviewReady
|
||||
current.Playback.PreviewURL = fmt.Sprintf("/media/assets/sessions/%s/preview.webm", session.ID)
|
||||
current.Segments = []SegmentMeta{
|
||||
{
|
||||
Sequence: 1,
|
||||
Filename: "000001.webm",
|
||||
DurationMS: 10000,
|
||||
SizeBytes: int64(len("segment-one")),
|
||||
ContentType: "video/webm",
|
||||
},
|
||||
{
|
||||
Sequence: 2,
|
||||
Filename: "000002.webm",
|
||||
DurationMS: 10000,
|
||||
SizeBytes: int64(len("segment-two")),
|
||||
ContentType: "video/webm",
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("updateSession: %v", err)
|
||||
}
|
||||
|
||||
fakeFFmpeg := filepath.Join(tempDir, "ffmpeg")
|
||||
script := "#!/bin/sh\nexit 1\n"
|
||||
if err := os.WriteFile(fakeFFmpeg, []byte(script), 0o755); err != nil {
|
||||
t.Fatalf("write fake ffmpeg: %v", err)
|
||||
}
|
||||
t.Setenv("PATH", tempDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
|
||||
if err := processRollingPreview(store, session.ID); err == nil {
|
||||
t.Fatalf("expected processRollingPreview to surface failure")
|
||||
}
|
||||
|
||||
current, err := store.getSession(session.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("getSession: %v", err)
|
||||
}
|
||||
if current.PreviewStatus != PreviewReady {
|
||||
t.Fatalf("expected previous preview to remain ready, got %s", current.PreviewStatus)
|
||||
}
|
||||
if current.Playback.PreviewURL == "" {
|
||||
t.Fatalf("expected preview url to remain available")
|
||||
}
|
||||
if current.LastError == "" {
|
||||
t.Fatalf("expected last error to be recorded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneExpiredRelaySessionsRemovesOldCache(t *testing.T) {
|
||||
store, err := newSessionStore(t.TempDir())
|
||||
if err != nil {
|
||||
|
||||
在新工单中引用
屏蔽一个用户