Improve live camera relay buffering
这个提交包含在:
@@ -2,12 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMediaHealthAndSessionLifecycle(t *testing.T) {
|
||||
@@ -320,3 +323,130 @@ func TestLiveFrameUploadPublishesRelayFrame(t *testing.T) {
|
||||
t.Fatalf("unexpected live frame content: %q", string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelaySegmentUploadKeepsOnlyLatestMinute(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 Buffer", Purpose: "relay"})
|
||||
if err != nil {
|
||||
t.Fatalf("createSession: %v", err)
|
||||
}
|
||||
|
||||
for sequence := 0; sequence < 3; sequence += 1 {
|
||||
req := httptest.NewRequest(http.MethodPost, "/media/sessions/"+session.ID+"/segments?sequence="+strconv.Itoa(sequence)+"&durationMs=30000", strings.NewReader("segment"))
|
||||
req.Header.Set("Content-Type", "video/webm")
|
||||
res := httptest.NewRecorder()
|
||||
server.routes().ServeHTTP(res, req)
|
||||
if res.Code != http.StatusAccepted {
|
||||
t.Fatalf("expected segment upload 202 for sequence %d, got %d", sequence, res.Code)
|
||||
}
|
||||
}
|
||||
|
||||
current, err := store.getSession(session.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("getSession: %v", err)
|
||||
}
|
||||
if current.Purpose != PurposeRelay {
|
||||
t.Fatalf("expected relay purpose, got %s", current.Purpose)
|
||||
}
|
||||
if len(current.Segments) != 2 {
|
||||
t.Fatalf("expected latest 2 relay segments to remain, got %d", len(current.Segments))
|
||||
}
|
||||
if current.Segments[0].Sequence != 1 || current.Segments[1].Sequence != 2 {
|
||||
t.Fatalf("expected relay segments 1 and 2 to remain, got %#v", current.Segments)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(store.segmentsDir(session.ID), "000000.webm")); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("expected earliest relay segment to be pruned from disk, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessRelayPreviewPublishesBufferedWebM(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 Preview", Purpose: "relay"})
|
||||
if err != nil {
|
||||
t.Fatalf("createSession: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000000.webm"), []byte("segment"), 0o644); err != nil {
|
||||
t.Fatalf("write segment: %v", err)
|
||||
}
|
||||
|
||||
if _, err := store.updateSession(session.ID, func(current *Session) error {
|
||||
current.Segments = append(current.Segments, SegmentMeta{
|
||||
Sequence: 0,
|
||||
Filename: "000000.webm",
|
||||
DurationMS: 60000,
|
||||
SizeBytes: 7,
|
||||
ContentType: "video/webm",
|
||||
})
|
||||
current.Purpose = PurposeRelay
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("updateSession: %v", err)
|
||||
}
|
||||
|
||||
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.Playback.PreviewURL == "" || !strings.HasSuffix(current.Playback.PreviewURL, "/preview.webm") {
|
||||
t.Fatalf("expected relay preview webm url, got %#v", current.Playback)
|
||||
}
|
||||
if current.Playback.MP4URL != "" {
|
||||
t.Fatalf("expected relay preview to skip mp4 generation, got %#v", current.Playback)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneExpiredRelaySessionsRemovesOldCache(t *testing.T) {
|
||||
store, err := newSessionStore(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("newSessionStore: %v", err)
|
||||
}
|
||||
|
||||
session, err := store.createSession(CreateSessionRequest{UserID: "1", Title: "Old Relay", Purpose: "relay"})
|
||||
if err != nil {
|
||||
t.Fatalf("createSession: %v", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(store.segmentsDir(session.ID), "000000.webm"), []byte("segment"), 0o644); err != nil {
|
||||
t.Fatalf("write segment: %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("preview"), 0o644); err != nil {
|
||||
t.Fatalf("write preview: %v", err)
|
||||
}
|
||||
|
||||
store.mu.Lock()
|
||||
store.sessions[session.ID].Purpose = PurposeRelay
|
||||
store.sessions[session.ID].UpdatedAt = time.Now().UTC().Add(-31 * time.Minute).Format(time.RFC3339)
|
||||
store.mu.Unlock()
|
||||
|
||||
if err := store.pruneExpiredRelaySessions(relayCacheTTL, time.Now().UTC()); err != nil {
|
||||
t.Fatalf("pruneExpiredRelaySessions: %v", err)
|
||||
}
|
||||
|
||||
if _, err := store.getSession(session.ID); err == nil {
|
||||
t.Fatalf("expected relay session to be removed from store")
|
||||
}
|
||||
if _, err := os.Stat(store.sessionDir(session.ID)); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("expected relay session directory to be removed, got %v", err)
|
||||
}
|
||||
if _, err := os.Stat(store.publicDir(session.ID)); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("expected relay public directory to be removed, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户