Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions ui/model/seek.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ type seekTickMsg struct{}

type ytdlUnpauseReconnectMsg struct{ err error }

// doSeek handles a seek keypress. For yt-dlp streams, accumulates into a
// single target position and debounces. For HTTP seekable streams, dispatches
// the seek asynchronously to avoid blocking the UI. For local files, seeks
// immediately.
// doSeek handles a seek keypress. Seeks that restart a decoder accumulate into
// one target and debounce; local files seek immediately.
func (m *Model) doSeek(d time.Duration) tea.Cmd {
return m.seekRelative(d, seekDebounceTicks)
}
Expand All @@ -46,7 +44,7 @@ func (m *Model) seekRelative(d time.Duration, debounceTicks int) tea.Cmd {
if m.player.IsStreamSeek() {
return m.streamSeekRelative(d)
}
if !m.player.IsYTDLSeek() {
if !m.needsDebouncedSeek() {
m.player.Seek(d)
m.finishSeek()
return nil
Expand All @@ -56,26 +54,38 @@ func (m *Model) seekRelative(d time.Duration, debounceTicks int) tea.Cmd {
if m.seek.active && debounceTicks > 0 {
target = m.seek.targetPos
}
return m.queueYTDLSeekTarget(target+d, debounceTicks)
return m.queueSeekTarget(target+d, debounceTicks)
}

// needsDebouncedSeek reports whether seeking restarts a decoder, making a burst
// of keypresses worth summing into one.
func (m *Model) needsDebouncedSeek() bool {
if m.player.IsYTDLSeek() {
return true
}
track, _ := m.currentPlaybackTrack()
return track.Stream && m.player.Seekable()
}

func (m *Model) seekAbsolute(target time.Duration) tea.Cmd {
if m.player.IsStreamSeek() {
return m.streamSeekAbsolute(target)
}
if !m.player.IsYTDLSeek() {
if !m.needsDebouncedSeek() {
m.player.Seek(target - m.player.Position())
m.finishSeek()
return nil
}
return m.queueYTDLSeekTarget(target, 0)
return m.queueSeekTarget(target, 0)
}

func (m *Model) queueYTDLSeekTarget(target time.Duration, debounceTicks int) tea.Cmd {
func (m *Model) queueSeekTarget(target time.Duration, debounceTicks int) tea.Cmd {
m.seek.active = true
m.seek.targetPos = m.clampPosition(target)

m.player.CancelSeekYTDL()
if m.player.IsYTDLSeek() {
m.player.CancelSeekYTDL()
}

if debounceTicks > 0 {
m.seek.timer = debounceTicks
Expand All @@ -85,7 +95,7 @@ func (m *Model) queueYTDLSeekTarget(target time.Duration, debounceTicks int) tea

m.seek.timer = 0
m.seek.timerFor = 0
return m.commitPendingYTDLSeek()
return m.commitPendingSeek()
}

func (m *Model) finishSeek() {
Expand All @@ -99,14 +109,19 @@ func (m *Model) finishSeek() {
})
}

func (m *Model) commitPendingYTDLSeek() tea.Cmd {
func (m *Model) commitPendingSeek() tea.Cmd {
target := m.seek.targetPos
curPos := m.player.Position()
d := target - curPos

p := m.player
ytdl := p.IsYTDLSeek()
return func() tea.Msg {
p.SeekYTDL(d)
if ytdl {
p.SeekYTDL(d)
} else {
p.Seek(d)
}
return seekTickMsg{}
}
}
Expand Down Expand Up @@ -134,5 +149,5 @@ func (m *Model) tickSeek(dt time.Duration) tea.Cmd {
}

// Timer expired — fire the seek to the target position.
return m.commitPendingYTDLSeek()
return m.commitPendingSeek()
}
Loading