package playback import ( "encoding/base32" "fmt" "git.dayanhub.com/sagi/subsonic-tui/internal/client" "github.com/delucks/go-subsonic" "github.com/godbus/dbus/v5" "github.com/quarckster/go-mpris-server/pkg/events" "github.com/quarckster/go-mpris-server/pkg/server" mprisTypes "github.com/quarckster/go-mpris-server/pkg/types" ) const ( mprisPlayerNmae = "SubsonicTUI" mprisNoTrack = "/org/mpris/MediaPlayer2/TrackList/NoTrack" ) type mprisRoot struct{} func (r mprisRoot) Raise() error { return nil } func (r mprisRoot) Quit() error { return nil } func (r mprisRoot) CanQuit() (bool, error) { return true, nil } func (r mprisRoot) CanRaise() (bool, error) { return false, nil } func (r mprisRoot) HasTrackList() (bool, error) { return false, nil } func (r mprisRoot) Identity() (string, error) { return mprisPlayerNmae, nil } func (r mprisRoot) SupportedUriSchemes() ([]string, error) { return []string{}, nil } func (r mprisRoot) SupportedMimeTypes() ([]string, error) { return []string{}, nil } type mprisPlayer struct { ctrl *Controller } // Implement other methods of `pkg.types.OrgMprisMediaPlayer2PlayerAdapter`. func (p mprisPlayer) Next() error { p.ctrl.Next() return nil } func (p mprisPlayer) Previous() error { p.ctrl.Prev() return nil } func (p mprisPlayer) Pause() error { if p.ctrl.State() == PlaybackStatePlaying { p.ctrl.TogglePlayPause() } return nil } func (p mprisPlayer) PlayPause() error { switch p.ctrl.State() { case PlaybackStatePaused, PlaybackStatePlaying: p.ctrl.TogglePlayPause() case PlaybackStateStopped: p.ctrl.Play(p.ctrl.GetCurrentSong()) } return nil } func (p mprisPlayer) Stop() error { p.ctrl.Stop() return nil } func (p mprisPlayer) Play() error { switch p.ctrl.State() { case PlaybackStatePaused: p.ctrl.TogglePlayPause() case PlaybackStateStopped: p.ctrl.Play(p.ctrl.GetCurrentSong()) } return nil } func (p mprisPlayer) Seek(offset mprisTypes.Microseconds) error { return nil } func (p mprisPlayer) SetPosition(trackId string, position mprisTypes.Microseconds) error { return nil } func (p mprisPlayer) OpenUri(uri string) error { return nil } func (p mprisPlayer) PlaybackStatus() (mprisTypes.PlaybackStatus, error) { switch p.ctrl.State() { case PlaybackStatePlaying: return mprisTypes.PlaybackStatusPlaying, nil case PlaybackStatePaused: return mprisTypes.PlaybackStatusPaused, nil case PlaybackStateStopped: return mprisTypes.PlaybackStatusStopped, nil } // Should not get here return mprisTypes.PlaybackStatusStopped, nil } func (p mprisPlayer) Rate() (float64, error) { return 1, nil } func (p mprisPlayer) SetRate(float64) error { return nil } func (p mprisPlayer) Metadata() (mprisTypes.Metadata, error) { s := p.ctrl.GetCurrentSong() objPath := mprisNoTrack if s != nil { objPath = encodeTrackID(s.ID) } else { s = &subsonic.Child{} } md := mprisTypes.Metadata{ TrackId: dbus.ObjectPath(objPath), Length: secondsToMicroseconds(s.Duration), Title: s.Title, Album: s.Album, Artist: []string{s.Artist}, DiscNumber: s.DiscNumber, Genre: []string{s.Genre}, TrackNumber: s.Track, UserRating: float64(s.UserRating), UseCount: int(s.PlayCount), } artw := client.ArtCache.GetPath(s.CoverArt) if artw != nil { md.ArtUrl = "file://" + *artw } return md, nil } func (p mprisPlayer) Volume() (float64, error) { return 1, nil } func (p mprisPlayer) SetVolume(float64) error { return nil } func (p mprisPlayer) Position() (int64, error) { return int64(secondsToMicroseconds(int(p.ctrl.position))), nil } func (p mprisPlayer) MinimumRate() (float64, error) { return 1, nil } func (p mprisPlayer) MaximumRate() (float64, error) { return 1, nil } func (p mprisPlayer) CanGoNext() (bool, error) { return p.ctrl.queue.HasNext(), nil } func (p mprisPlayer) CanGoPrevious() (bool, error) { return p.ctrl.queue.HasPrev(), nil } func (p mprisPlayer) CanPlay() (bool, error) { return p.ctrl.GetCurrentSong() != nil, nil } func (p mprisPlayer) CanPause() (bool, error) { return true, nil } func (p mprisPlayer) CanSeek() (bool, error) { return false, nil } func (p mprisPlayer) CanControl() (bool, error) { return true, nil } var _ DesktopPlayback = &mprisPlayback{} type mprisPlayback struct { root mprisRoot player mprisPlayer eventHandler *events.EventHandler server *server.Server err error } func (p *mprisPlayback) Start() { go func() { p.err = p.server.Listen() }() } func (p *mprisPlayback) Stop() error { return p.server.Stop() } func (p *mprisPlayback) OnPlayPause() { if p.err != nil { return } p.err = p.eventHandler.Player.OnPlayPause() } func (p *mprisPlayback) OnPlaylistChanged() { if p.err != nil { return } p.err = p.eventHandler.Player.OnOptions() } func (p *mprisPlayback) OnSongChanged() { if p.err != nil { return } p.err = p.eventHandler.Player.OnTitle() } func (p *mprisPlayback) OnPositionChanged(position int) { if p.err != nil { return } p.err = p.eventHandler.Player.OnSeek(secondsToMicroseconds(position)) if p.err != nil { return } p.err = p.eventHandler.Player.OnOptions() } func desktopPlayer(c *Controller) DesktopPlayback { r := mprisRoot{} p := mprisPlayer{ ctrl: c, } s := server.NewServer(mprisPlayerNmae, r, p) ev := events.NewEventHandler(s) return &mprisPlayback{ root: r, player: p, server: s, eventHandler: ev, } } func secondsToMicroseconds(s int) mprisTypes.Microseconds { return mprisTypes.Microseconds(s * 1_000_000) } func encodeTrackID(id string) string { data := []byte(id) return fmt.Sprintf("/%s/Track/%s", mprisPlayerNmae, base32.StdEncoding.WithPadding('0').EncodeToString(data)) }