Skip to content

Commit de0794d

Browse files
authored
fix(ui): return stream seek cmds from arrow keys (#169)
Arrow-key seeking for HTTP podcast streams never ran because the key handlers called m.doSeek(...) without returning its command. That meant seek-by-reconnect worked via IPC and jump-to-time, but not via Left/Right in the TUI. Return the command from the arrow-key handlers and add a regression test for the HTTP stream seek path.
1 parent 36e5d64 commit de0794d

2 files changed

Lines changed: 75 additions & 4 deletions

File tree

ui/model/keys.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,23 +413,23 @@ func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd {
413413
m.eqCursor--
414414
}
415415
} else {
416-
m.doSeek(-5 * time.Second)
416+
return m.doSeek(-5 * time.Second)
417417
}
418418

419419
case "shift+left":
420-
m.doSeek(-m.seekStepLarge)
420+
return m.doSeek(-m.seekStepLarge)
421421

422422
case "right":
423423
if m.focus == focusEQ {
424424
if m.eqCursor < eqBandCount-1 {
425425
m.eqCursor++
426426
}
427427
} else {
428-
m.doSeek(5 * time.Second)
428+
return m.doSeek(5 * time.Second)
429429
}
430430

431431
case "shift+right":
432-
m.doSeek(m.seekStepLarge)
432+
return m.doSeek(m.seekStepLarge)
433433

434434
case "*":
435435
if m.focus == focusPlaylist && m.plCursor >= 0 && m.plCursor < m.playlist.Len() && m.loadedPlaylist != "" {

ui/model/stream_seek_keys_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package model
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
tea "charm.land/bubbletea/v2"
8+
)
9+
10+
type fakeEngine struct {
11+
streamSeek bool
12+
seekCalls []time.Duration
13+
}
14+
15+
func (f *fakeEngine) Play(string, time.Duration) error { return nil }
16+
func (f *fakeEngine) PlayYTDL(string, time.Duration) error { return nil }
17+
func (f *fakeEngine) Preload(string, time.Duration) error { return nil }
18+
func (f *fakeEngine) PreloadYTDL(string, time.Duration) error { return nil }
19+
func (f *fakeEngine) ClearPreload() {}
20+
func (f *fakeEngine) Stop() {}
21+
func (f *fakeEngine) Close() {}
22+
func (f *fakeEngine) TogglePause() {}
23+
func (f *fakeEngine) Seek(d time.Duration) error { f.seekCalls = append(f.seekCalls, d); return nil }
24+
func (f *fakeEngine) SeekYTDL(time.Duration) error { return nil }
25+
func (f *fakeEngine) CancelSeekYTDL() {}
26+
func (f *fakeEngine) IsPlaying() bool { return true }
27+
func (f *fakeEngine) IsPaused() bool { return false }
28+
func (f *fakeEngine) Drained() bool { return false }
29+
func (f *fakeEngine) HasPreload() bool { return false }
30+
func (f *fakeEngine) Seekable() bool { return f.streamSeek }
31+
func (f *fakeEngine) IsStreamSeek() bool { return f.streamSeek }
32+
func (f *fakeEngine) IsYTDLSeek() bool { return false }
33+
func (f *fakeEngine) GaplessAdvanced() bool { return false }
34+
func (f *fakeEngine) Position() time.Duration { return 0 }
35+
func (f *fakeEngine) Duration() time.Duration { return time.Hour }
36+
func (f *fakeEngine) PositionAndDuration() (time.Duration, time.Duration) { return 0, time.Hour }
37+
func (f *fakeEngine) SetVolume(float64) {}
38+
func (f *fakeEngine) Volume() float64 { return 0 }
39+
func (f *fakeEngine) SetSpeed(float64) {}
40+
func (f *fakeEngine) Speed() float64 { return 1 }
41+
func (f *fakeEngine) ToggleMono() {}
42+
func (f *fakeEngine) Mono() bool { return false }
43+
func (f *fakeEngine) SetEQBand(int, float64) {}
44+
func (f *fakeEngine) EQBands() [10]float64 { return [10]float64{} }
45+
func (f *fakeEngine) StreamErr() error { return nil }
46+
func (f *fakeEngine) StreamTitle() string { return "" }
47+
func (f *fakeEngine) StreamBytes() (downloaded, total int64) { return 0, 0 }
48+
func (f *fakeEngine) SamplesInto([]float64) int { return 0 }
49+
func (f *fakeEngine) SampleRate() int { return 44100 }
50+
51+
func TestHandleKeyRightReturnsCmdForHTTPStreamSeek(t *testing.T) {
52+
eng := &fakeEngine{streamSeek: true}
53+
m := Model{player: eng}
54+
55+
cmd := m.handleKey(tea.KeyPressMsg{Code: tea.KeyRight})
56+
if cmd == nil {
57+
t.Fatal("handleKey(right) cmd = nil, want seek cmd for HTTP stream")
58+
}
59+
60+
msg := cmd()
61+
if _, ok := msg.(seekTickMsg); !ok {
62+
t.Fatalf("cmd() msg = %T, want seekTickMsg", msg)
63+
}
64+
65+
if len(eng.seekCalls) != 1 {
66+
t.Fatalf("Seek call count = %d, want 1", len(eng.seekCalls))
67+
}
68+
if got := eng.seekCalls[0]; got != 5*time.Second {
69+
t.Fatalf("Seek arg = %v, want %v", got, 5*time.Second)
70+
}
71+
}

0 commit comments

Comments
 (0)