diff --git a/backend/app.go b/backend/app.go index 4c5e9cc..2390eaf 100644 --- a/backend/app.go +++ b/backend/app.go @@ -398,6 +398,8 @@ func (a *App) checkFlagsAndSendIPCMsg(cli *ipc.Client) error { return cli.SetVolume(VolumeCLIArg) case SeekToCLIArg >= 0: return cli.SeekSeconds(SeekToCLIArg) + case SeekByCLIArg != 0: + return cli.SeekBySeconds(SeekByCLIArg) default: return nil } diff --git a/backend/cmdlineoptions.go b/backend/cmdlineoptions.go index 5e189ed..267ed7a 100644 --- a/backend/cmdlineoptions.go +++ b/backend/cmdlineoptions.go @@ -8,6 +8,7 @@ import ( var ( VolumeCLIArg int = -1 SeekToCLIArg float64 = -1 + SeekByCLIArg float64 = 0 FlagPlay = flag.Bool("play", false, "unpause or begin playback") FlagPause = flag.Bool("pause", false, "pause playback") @@ -30,6 +31,11 @@ func init() { SeekToCLIArg = v return err }) + flag.Func("seek-by", "seeks back or forward by the given number of seconds (negative or positive)", func(s string) error { + v, err := strconv.ParseFloat(s, 64) + SeekByCLIArg = v + return err + }) } func HaveCommandLineOptions() bool { diff --git a/backend/ipc/api.go b/backend/ipc/api.go index fbea445..f7eba39 100644 --- a/backend/ipc/api.go +++ b/backend/ipc/api.go @@ -11,6 +11,7 @@ const ( PreviousPath = "/transport/previous" NextPath = "/transport/next" TimePosPath = "/transport/timepos" // ?s= + SeekByPath = "/transport/seek-by" // ?s=<+/- seconds> VolumePath = "/volume" // ?v= ShowPath = "/window/show" QuitPath = "/window/quit" @@ -27,3 +28,7 @@ func SetVolumePath(vol int) string { func SeekToSecondsPath(secs float64) string { return fmt.Sprintf("%s?s=%0.2f", TimePosPath, secs) } + +func SeekBySecondsPath(secs float64) string { + return fmt.Sprintf("%s?s=%0.2f", SeekByPath, secs) +} diff --git a/backend/ipc/client.go b/backend/ipc/client.go index b342382..755183a 100644 --- a/backend/ipc/client.go +++ b/backend/ipc/client.go @@ -60,6 +60,10 @@ func (c *Client) SeekSeconds(secs float64) error { return c.sendRequest(SeekToSecondsPath(secs)) } +func (c *Client) SeekBySeconds(secs float64) error { + return c.sendRequest(SeekBySecondsPath(secs)) +} + func (c *Client) SetVolume(vol int) error { return c.sendRequest(SetVolumePath(vol)) } diff --git a/backend/ipc/server.go b/backend/ipc/server.go index fc62ed3..fa32ba8 100644 --- a/backend/ipc/server.go +++ b/backend/ipc/server.go @@ -16,6 +16,7 @@ type PlaybackHandler interface { SeekBackOrPrevious() error SeekNext() error SeekSeconds(float64) error + SeekBySeconds(float64) error Volume() int SetVolume(int) error } @@ -71,14 +72,8 @@ func (s *serverImpl) createHandler() http.Handler { m.HandleFunc(StopPath, s.makeSimpleEndpointHandler(s.pbHandler.Stop)) m.HandleFunc(PreviousPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekBackOrPrevious)) m.HandleFunc(NextPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekNext)) - m.HandleFunc(TimePosPath, func(w http.ResponseWriter, r *http.Request) { - _s := r.URL.Query().Get("s") - if secs, err := strconv.ParseFloat(_s, 64); err == nil { - s.writeSimpleResponse(w, s.pbHandler.SeekSeconds(secs)) - } else { - s.writeErr(w, err) - } - }) + m.HandleFunc(TimePosPath, s.makeFloatEndpointHandler(s.pbHandler.SeekSeconds, "s")) + m.HandleFunc(SeekByPath, s.makeFloatEndpointHandler(s.pbHandler.SeekBySeconds, "s")) m.HandleFunc(VolumePath, func(w http.ResponseWriter, r *http.Request) { v := r.URL.Query().Get("v") if vol, err := strconv.Atoi(v); err == nil { @@ -96,6 +91,17 @@ func (s *serverImpl) makeSimpleEndpointHandler(f func() error) func(http.Respons } } +func (s *serverImpl) makeFloatEndpointHandler(f func(float64) error, queryParam string) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + v := r.URL.Query().Get(queryParam) + if val, err := strconv.ParseFloat(v, 64); err == nil { + s.writeSimpleResponse(w, f(val)) + } else { + s.writeErr(w, err) + } + } +} + func (s *serverImpl) writeSimpleResponse(w http.ResponseWriter, err error) { if err == nil { s.writeOK(w) diff --git a/backend/mpris.go b/backend/mpris.go index 440747c..7be7121 100644 --- a/backend/mpris.go +++ b/backend/mpris.go @@ -184,8 +184,7 @@ func (m *MPRISHandler) Play() error { func (m *MPRISHandler) Seek(offset types.Microseconds) error { // MPRIS seek command is relative to current position - pos := m.pm.PlayerStatus().TimePos + microsecondsToSeconds(offset) - return m.pm.SeekSeconds(pos) + return m.pm.SeekBySeconds(microsecondsToSeconds(offset)) } func (m *MPRISHandler) SetPosition(trackId string, position types.Microseconds) error { diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 95005ad..94f8a3b 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -284,6 +284,18 @@ func (p *PlaybackManager) SeekSeconds(sec float64) error { return p.engine.SeekSeconds(sec) } +// Seek by given relative position in the current track by seconds. +func (p *PlaybackManager) SeekBySeconds(sec float64) error { + status := p.engine.PlayerStatus() + target := status.TimePos + sec + if target < 0 { + target = 0 + } else if target > status.Duration { + target = status.Duration + } + return p.engine.SeekSeconds(target) +} + // Seek to a fractional position in the current track [0..1] func (p *PlaybackManager) SeekFraction(fraction float64) error { if fraction < 0 {