124 lines
5.9 KiB
Go
124 lines
5.9 KiB
Go
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)
|
|
}
|
|
}
|