Skip to content

Commit 4e3e924

Browse files
committed
Enhance SuperFlix video playback and caching mechanisms
- Added tests to ensure proper handling of HLS demuxer arguments based on video source type. - Improved error handling in player menu and resume dialog functions. - Refactored video URL extraction logic to prefer unsigned HLS playlists over secured links. - Implemented a CDN liveness probe to validate stream URLs and evict stale cache entries. - Updated subtitle argument handling to ensure one `--sub-file=` per track, avoiding issues with URL separators. - Enhanced test coverage for stream caching and subtitle argument generation.
1 parent 95dc097 commit 4e3e924

24 files changed

Lines changed: 1033 additions & 191 deletions

internal/api/enhanced.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,6 @@ func GetSuperFlixStreamURL(media *models.Anime, episode *models.Episode, quality
790790
if result.Referer != "" {
791791
util.SetGlobalReferer(result.Referer)
792792
}
793-
794793
// Update cover image from stream thumbnail if not already set
795794
if media.ImageURL == "" && result.Thumb != "" {
796795
media.ImageURL = result.Thumb

internal/api/superflix_servers.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/alvarorichard/Goanime/internal/scraper/providers/superflix"
99
"github.com/alvarorichard/Goanime/internal/tui"
1010
"github.com/alvarorichard/Goanime/internal/util"
11-
"github.com/ktr0731/go-fuzzyfinder"
1211
)
1312

1413
// SuperFlix offers, per episode, a list of servers, and each server is tagged
@@ -26,8 +25,12 @@ import (
2625
// sfPickFn is the single selection seam for every SuperFlix prompt. A package var
2726
// so tests drive the whole flow without a TTY.
2827
var sfPickFn = func(prompt string, labels []string) (int, error) {
29-
return tui.Find(labels, func(i int) string { return labels[i] },
30-
fuzzyfinder.WithPromptString(prompt))
28+
return tui.PickLabels(labels, tui.PickOptions{
29+
Breadcrumb: "SuperFlix > " + prompt,
30+
WindowTitle: "GoAnime - SuperFlix",
31+
ItemSingular: "option",
32+
ItemPlural: "options",
33+
})
3134
}
3235

3336
// audioKindName is the site's own word for an audio type.

internal/playback/input.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package playback
22

33
import (
4+
"errors"
5+
46
"github.com/alvarorichard/Goanime/internal/tui"
57
"github.com/alvarorichard/Goanime/internal/util"
6-
"github.com/ktr0731/go-fuzzyfinder"
78
)
89

910
// menuItem maps a display label to the short code returned by GetUserInput.
@@ -12,10 +13,19 @@ type menuItem struct {
1213
Value string
1314
}
1415

15-
// findMenuFunc is a package-level indirection over tui.Find so tests can
16-
// drive GetUserInput without opening a TUI.
17-
var findMenuFunc = func(items []menuItem, itemFunc func(i int) string, opts ...fuzzyfinder.Option) (int, error) {
18-
return tui.Find(items, itemFunc, opts...)
16+
// findMenuFunc is a package-level indirection over the fancy picker so tests
17+
// can drive GetUserInput without opening a TTY.
18+
var findMenuFunc = func(items []menuItem) (int, error) {
19+
labels := make([]string, len(items))
20+
for i, it := range items {
21+
labels[i] = it.Label
22+
}
23+
return tui.PickLabels(labels, tui.PickOptions{
24+
Breadcrumb: "Playback > Next",
25+
WindowTitle: "GoAnime - Menu",
26+
ItemSingular: "option",
27+
ItemPlural: "options",
28+
})
1929
}
2030

2131
// GetUserInput shows post-playback menu. Pass isMovie=true for movies to show
@@ -44,16 +54,19 @@ func GetUserInput(isMovie ...bool) string {
4454
}
4555
}
4656

47-
idx, err := findMenuFunc(items, func(i int) string {
48-
return items[i].Label
49-
}, fuzzyfinder.WithPromptString("What would you like to do next? "))
57+
idx, err := findMenuFunc(items)
5058
if err != nil {
51-
// A broken or aborted menu must not auto-advance: returning "n" here
52-
// made HandleSeries/HandleMovie auto-play forever on non-TTY
53-
// terminals and turned Esc into "next episode".
59+
// Esc/back from the fancy picker is an intentional exit path.
60+
if errors.Is(err, tui.ErrPickBack) {
61+
return "back"
62+
}
63+
// A broken menu must not auto-advance: returning "n" here made
64+
// HandleSeries/HandleMovie auto-play forever on non-TTY terminals.
5465
util.Errorf("Error showing menu: %v", err)
5566
return "q"
5667
}
57-
68+
if idx < 0 || idx >= len(items) {
69+
return "q"
70+
}
5871
return items[idx].Value
5972
}

internal/playback/input_test.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,56 @@ import (
44
"errors"
55
"testing"
66

7-
"github.com/ktr0731/go-fuzzyfinder"
7+
"github.com/alvarorichard/Goanime/internal/tui"
88
"github.com/stretchr/testify/assert"
99
"github.com/stretchr/testify/require"
1010
)
1111

1212
// swapFindMenu replaces the menu finder seam for the duration of the test.
1313
// Tests using it mutate a package global, so they must not run in parallel.
14-
func swapFindMenu(t *testing.T, fn func(items []menuItem, itemFunc func(i int) string, opts ...fuzzyfinder.Option) (int, error)) {
14+
func swapFindMenu(t *testing.T, fn func(items []menuItem) (int, error)) {
1515
t.Helper()
1616
orig := findMenuFunc
1717
findMenuFunc = fn
1818
t.Cleanup(func() { findMenuFunc = orig })
1919
}
2020

2121
func TestGetUserInput_MenuErrorDoesNotAutoAdvance(t *testing.T) {
22-
// Regression: on menu failure (non-TTY, Esc, broken terminal) GetUserInput
22+
// Regression: on menu failure (non-TTY, broken terminal) GetUserInput
2323
// returned "n", which made HandleSeries/HandleMovie auto-play the next
2424
// episode forever with zero user input. Failure must quit.
25-
swapFindMenu(t, func(_ []menuItem, _ func(i int) string, _ ...fuzzyfinder.Option) (int, error) {
25+
swapFindMenu(t, func(_ []menuItem) (int, error) {
2626
return 0, errors.New("failed to open TTY")
2727
})
2828
assert.Equal(t, "q", GetUserInput(), "menu failure must map to quit, never to next-episode")
2929
assert.Equal(t, "q", GetUserInput(true), "movie menu failure must map to quit, never to replay")
3030
}
3131

32+
func TestGetUserInput_PickBackMapsToBack(t *testing.T) {
33+
swapFindMenu(t, func(_ []menuItem) (int, error) {
34+
return -1, tui.ErrPickBack
35+
})
36+
assert.Equal(t, "back", GetUserInput())
37+
assert.Equal(t, "back", GetUserInput(true))
38+
}
39+
40+
func TestGetUserInput_PickCancelMapsToQuit(t *testing.T) {
41+
swapFindMenu(t, func(_ []menuItem) (int, error) {
42+
return -1, tui.ErrPickCancelled
43+
})
44+
assert.Equal(t, "q", GetUserInput())
45+
}
46+
3247
func TestGetUserInput_SeriesMenuMapping(t *testing.T) {
3348
wantLabels := []string{"Next episode", "Previous episode", "Select episode", "Change anime", "← Back", "Quit"}
3449
wantValues := []string{"n", "p", "e", "c", "back", "q"}
3550

3651
for i, wantValue := range wantValues {
3752
var gotLabels []string
38-
swapFindMenu(t, func(items []menuItem, itemFunc func(i int) string, _ ...fuzzyfinder.Option) (int, error) {
53+
swapFindMenu(t, func(items []menuItem) (int, error) {
3954
gotLabels = nil
40-
for j := range items {
41-
gotLabels = append(gotLabels, itemFunc(j))
55+
for _, it := range items {
56+
gotLabels = append(gotLabels, it.Label)
4257
}
4358
return i, nil
4459
})
@@ -54,10 +69,10 @@ func TestGetUserInput_MovieMenuMapping(t *testing.T) {
5469

5570
for i, wantValue := range wantValues {
5671
var gotLabels []string
57-
swapFindMenu(t, func(items []menuItem, itemFunc func(i int) string, _ ...fuzzyfinder.Option) (int, error) {
72+
swapFindMenu(t, func(items []menuItem) (int, error) {
5873
gotLabels = nil
59-
for j := range items {
60-
gotLabels = append(gotLabels, itemFunc(j))
74+
for _, it := range items {
75+
gotLabels = append(gotLabels, it.Label)
6176
}
6277
return i, nil
6378
})
@@ -66,3 +81,10 @@ func TestGetUserInput_MovieMenuMapping(t *testing.T) {
6681
assert.Equal(t, wantValue, got, "label %q must map to %q", wantLabels[i], wantValue)
6782
}
6883
}
84+
85+
func TestGetUserInput_InvalidIndexQuits(t *testing.T) {
86+
swapFindMenu(t, func(_ []menuItem) (int, error) {
87+
return 99, nil
88+
})
89+
assert.Equal(t, "q", GetUserInput())
90+
}

internal/player/blogger_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ func TestFilterMPVArgs_DemuxerLavfOAllowed(t *testing.T) {
170170
"otherwise the HLS audio fix never reaches mpv and video plays silent")
171171
}
172172

173+
func TestFilterMPVArgs_ForcedHLSFormatAllowed(t *testing.T) {
174+
filtered := filterMPVArgs([]string{hlsForceLavfFormatArg})
175+
assert.Contains(t, filtered, hlsForceLavfFormatArg,
176+
"the forced HLS format for SuperFlix master.txt MUST reach mpv")
177+
}
178+
173179
func TestFilterMPVArgs_Whitelist(t *testing.T) {
174180
tests := []struct {
175181
name string

0 commit comments

Comments
 (0)