同步上游 zip:合并任务后台化 + 合并整剧可直接播放;保留 ultrafast+4线程 转码优化与 .gitignore 本地规则
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMergedPlaybackLocalRangeResumeWithoutEpisodes(t *testing.T) {
|
||||
app, fixture := nativePlaybackFixture(t, "6", "320x180")
|
||||
path := fixture.tasks[0].OutPath
|
||||
dramaID := fixture.tasks[0].DramaID
|
||||
id := mergedPlaybackPrefix + dramaID
|
||||
app.merges = map[string]*UIMergeState{dramaID: {DramaID: dramaID, DramaTitle: "合成全集", Status: "success", StartEpisode: 1, EndEpisode: 3, Merged: 3, OutputPath: path}}
|
||||
app.taskOrder = append([]string(nil), fixture.downloadIDs...)
|
||||
for _, taskID := range fixture.downloadIDs {
|
||||
app.removeTaskLocked(taskID)
|
||||
}
|
||||
if len(app.tasks) != 0 || app.merges[dramaID] == nil {
|
||||
t.Fatal("clearing the final episode task removed the merged file record")
|
||||
}
|
||||
app.statePath = filepath.Join(app.cfg.dataDirectory(), "ui-state.json")
|
||||
app.downloader.cfg.FFmpeg = filepath.Join(t.TempDir(), "ffmpeg-must-not-run")
|
||||
app.mediaResources().settings.Enabled = false
|
||||
app.mu.Lock()
|
||||
err := app.saveStateLocked()
|
||||
app.mu.Unlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
restored := &UIApp{cfg: app.cfg, statePath: app.statePath, tasks: map[string]*UITask{}}
|
||||
restored.loadState()
|
||||
if _, local, err := restored.mergedPlaybackTaskLocked(id); err != nil || local != path {
|
||||
t.Fatal("restart lost merged file without episode tasks", local, err)
|
||||
}
|
||||
open := func(body any) map[string]json.RawMessage {
|
||||
t.Helper()
|
||||
result := historyJSONRequest(t, app, app.handlePlaybackOpen, "/api/ui/playback/open", body)
|
||||
if result.Code != http.StatusOK {
|
||||
t.Fatal(result.Code, result.Body.String())
|
||||
}
|
||||
var response map[string]json.RawMessage
|
||||
if err := json.Unmarshal(result.Body.Bytes(), &response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return response
|
||||
}
|
||||
opened := open(map[string]any{"taskId": id, "resume": true})
|
||||
var sessionID string
|
||||
_ = json.Unmarshal(opened["session"], &sessionID)
|
||||
defer app.closePlaybacks()
|
||||
var episodes []playbackEpisode
|
||||
_ = json.Unmarshal(opened["episodes"], &episodes)
|
||||
if len(episodes) != 1 || !episodes[0].Merged || episodes[0].TaskID != id || episodes[0].Danmaku {
|
||||
t.Fatal("merged file did not appear as a standalone full-length selection", string(opened["episodes"]))
|
||||
}
|
||||
preparation := historyJSONRequest(t, app, app.handlePlaybackPrepare, "/api/ui/playback/prepare", map[string]any{"session": sessionID, "episode": 1})
|
||||
if preparation.Code != http.StatusOK || !strings.Contains(preparation.Body.String(), `"source":"merged"`) || len(app.tasks) != 0 {
|
||||
t.Fatal("merged playback attempted episode downloads", preparation.Code, preparation.Body.String())
|
||||
}
|
||||
result := historyJSONRequest(t, app, app.handlePlaybackPlan, "/api/ui/playback/plan", map[string]any{"session": sessionID, "episode": 1, "version": 1, "start": 2.0,
|
||||
"client": playbackClientCapabilities{MP4: true, Video: []string{"h264"}, Audio: []string{"aac"}}})
|
||||
var plan playbackMediaPlan
|
||||
if result.Code != http.StatusOK || json.Unmarshal(result.Body.Bytes(), &plan) != nil || plan.Delivery != "local" || plan.Player != "mp4" || plan.Processing != "original" {
|
||||
t.Fatal("local merged playback depended on the online optimization flag or FFmpeg", result.Code, result.Body.String())
|
||||
}
|
||||
body, _ := os.ReadFile(path)
|
||||
for _, test := range []struct {
|
||||
method, span string
|
||||
code, length int
|
||||
}{{"GET", "bytes=0-15", 206, 16}, {"GET", "bytes=-16", 206, 16}, {"HEAD", "", 200, 0}} {
|
||||
request := httptest.NewRequest(test.method, "http://localhost"+plan.URL, nil)
|
||||
request.Header.Set("Range", test.span)
|
||||
writer := httptest.NewRecorder()
|
||||
app.handlePlaybackMediaAsset(writer, viewerFixtureRequest(app, request))
|
||||
if writer.Code != test.code || writer.Body.Len() != test.length || test.span == "bytes=-16" && !bytes.Equal(writer.Body.Bytes(), body[len(body)-16:]) {
|
||||
t.Fatal("merged file did not serve local byte ranges", writer.Code, writer.Body.Len())
|
||||
}
|
||||
}
|
||||
session := app.playbacks[sessionID]
|
||||
if _, saved, err := app.recordPlaybackProgress(fixtureViewer(app), sessionID, playbackHistoryProgress{Run: session.run, Sequence: 1, Episode: 1, Position: 2.5, Duration: 6}); err != nil || !saved {
|
||||
t.Fatal("merged progress was not saved", saved, err)
|
||||
}
|
||||
app.closePlayback(sessionID)
|
||||
resumed := open(map[string]any{"dramaId": dramaID, "resume": true, "fromHistory": true})
|
||||
if string(resumed["initialPosition"]) != "2.5" || !bytes.Contains(resumed["episodes"], []byte(`"merged":true`)) {
|
||||
t.Fatal("merged history returned to online episodes or lost the full-file timestamp", string(resumed["initialPosition"]), string(resumed["episodes"]))
|
||||
}
|
||||
for _, scope := range []accountRecord{{OnlineOnly: true}, {Sources: []string{"no-access"}}} {
|
||||
body, _ := json.Marshal(map[string]string{"taskId": id})
|
||||
request := viewerFixtureRequest(app, httptest.NewRequest(http.MethodPost, "/api/ui/playback/open", bytes.NewReader(body)))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request = request.WithContext(withSourceScope(request.Context(), scope))
|
||||
writer := httptest.NewRecorder()
|
||||
app.handlePlaybackOpen(writer, request)
|
||||
if writer.Code != http.StatusForbidden {
|
||||
t.Fatal("merged playback bypassed source/download permissions", scope, writer.Code, writer.Body.String())
|
||||
}
|
||||
}
|
||||
other := app.browserViewers().acquire(viewerID("different-merged-viewer"))
|
||||
defer other.release()
|
||||
request := httptest.NewRequest(http.MethodGet, "http://localhost"+plan.URL, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
app.handlePlaybackMediaAsset(writer, request.WithContext(context.WithValue(request.Context(), viewerContextKey{}, other)))
|
||||
if writer.Code != http.StatusGone {
|
||||
t.Fatal("merged playback leaked to another viewer", writer.Code)
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := app.playbackCollectionTask(id); err == nil {
|
||||
t.Fatal("missing merged file fell back to upstream video")
|
||||
}
|
||||
if _, err := app.prepareCollectionDownload(context.Background(), id); err == nil || len(app.tasks) != 0 {
|
||||
t.Fatal("missing merged file created download tasks", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user