subsonic-tui/internal/playback/mpris.go
Sagi Dayan bae2c74815
initial commit
Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
2024-12-14 23:23:29 +02:00

249 lines
5.5 KiB
Go

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"
. "github.com/quarckster/go-mpris-server/pkg/types"
)
const (
mprisPlayerNmae = "MehSonic"
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 Microseconds) error {
return nil
}
func (p mprisPlayer) SetPosition(trackId string, position Microseconds) error {
return nil
}
func (p mprisPlayer) OpenUri(uri string) error {
return nil
}
func (p mprisPlayer) PlaybackStatus() (PlaybackStatus, error) {
switch p.ctrl.State() {
case PlaybackStatePlaying:
return PlaybackStatusPlaying, nil
case PlaybackStatePaused:
return PlaybackStatusPaused, nil
case PlaybackStateStopped:
return PlaybackStatusStopped, nil
}
// Should not get here
return PlaybackStatusStopped, nil
}
func (p mprisPlayer) Rate() (float64, error) {
return 1, nil
}
func (p mprisPlayer) SetRate(float64) error {
return nil
}
func (p mprisPlayer) Metadata() (Metadata, error) {
s := p.ctrl.GetCurrentSong()
objPath := mprisNoTrack
if s != nil {
objPath = encodeTrackId(s.ID)
} else {
s = &subsonic.Child{}
}
md := 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 = fmt.Sprintf("file://%s", *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) Microseconds {
return 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))
}