2024-01-16 15:21:35 +00:00
|
|
|
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"
|
2024-12-18 15:29:16 +00:00
|
|
|
mprisTypes "github.com/quarckster/go-mpris-server/pkg/types"
|
2024-01-16 15:21:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2024-12-18 15:29:16 +00:00
|
|
|
mprisPlayerNmae = "SubsonicTUI"
|
2024-01-16 15:21:35 +00:00
|
|
|
mprisNoTrack = "/org/mpris/MediaPlayer2/TrackList/NoTrack"
|
|
|
|
)
|
|
|
|
|
|
|
|
type mprisRoot struct{}
|
|
|
|
|
|
|
|
func (r mprisRoot) Raise() error {
|
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (r mprisRoot) Quit() error {
|
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (r mprisRoot) CanQuit() (bool, error) {
|
|
|
|
return true, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (r mprisRoot) CanRaise() (bool, error) {
|
|
|
|
return false, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (r mprisRoot) HasTrackList() (bool, error) {
|
|
|
|
return false, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (r mprisRoot) Identity() (string, error) {
|
|
|
|
return mprisPlayerNmae, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (r mprisRoot) SupportedUriSchemes() ([]string, error) {
|
|
|
|
return []string{}, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (r mprisRoot) SupportedMimeTypes() ([]string, error) {
|
|
|
|
return []string{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type mprisPlayer struct {
|
|
|
|
ctrl *Controller
|
|
|
|
}
|
|
|
|
|
2024-12-18 15:29:16 +00:00
|
|
|
// Implement other methods of `pkg.types.OrgMprisMediaPlayer2PlayerAdapter`.
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Next() error {
|
|
|
|
p.ctrl.Next()
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Previous() error {
|
|
|
|
p.ctrl.Prev()
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Pause() error {
|
|
|
|
if p.ctrl.State() == PlaybackStatePlaying {
|
|
|
|
p.ctrl.TogglePlayPause()
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) PlayPause() error {
|
|
|
|
switch p.ctrl.State() {
|
|
|
|
case PlaybackStatePaused, PlaybackStatePlaying:
|
|
|
|
p.ctrl.TogglePlayPause()
|
|
|
|
case PlaybackStateStopped:
|
|
|
|
p.ctrl.Play(p.ctrl.GetCurrentSong())
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Stop() error {
|
|
|
|
p.ctrl.Stop()
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Play() error {
|
|
|
|
switch p.ctrl.State() {
|
|
|
|
case PlaybackStatePaused:
|
|
|
|
p.ctrl.TogglePlayPause()
|
|
|
|
case PlaybackStateStopped:
|
|
|
|
p.ctrl.Play(p.ctrl.GetCurrentSong())
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
|
|
|
func (p mprisPlayer) Seek(offset mprisTypes.Microseconds) error {
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
|
|
|
func (p mprisPlayer) SetPosition(trackId string, position mprisTypes.Microseconds) error {
|
2024-01-16 15:21:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) OpenUri(uri string) error {
|
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
|
|
|
func (p mprisPlayer) PlaybackStatus() (mprisTypes.PlaybackStatus, error) {
|
2024-01-16 15:21:35 +00:00
|
|
|
switch p.ctrl.State() {
|
|
|
|
case PlaybackStatePlaying:
|
2024-12-18 15:29:16 +00:00
|
|
|
return mprisTypes.PlaybackStatusPlaying, nil
|
2024-01-16 15:21:35 +00:00
|
|
|
case PlaybackStatePaused:
|
2024-12-18 15:29:16 +00:00
|
|
|
return mprisTypes.PlaybackStatusPaused, nil
|
2024-01-16 15:21:35 +00:00
|
|
|
case PlaybackStateStopped:
|
2024-12-18 15:29:16 +00:00
|
|
|
return mprisTypes.PlaybackStatusStopped, nil
|
2024-01-16 15:21:35 +00:00
|
|
|
}
|
|
|
|
// Should not get here
|
2024-12-18 15:29:16 +00:00
|
|
|
return mprisTypes.PlaybackStatusStopped, nil
|
2024-01-16 15:21:35 +00:00
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Rate() (float64, error) {
|
|
|
|
return 1, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) SetRate(float64) error {
|
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
|
|
|
func (p mprisPlayer) Metadata() (mprisTypes.Metadata, error) {
|
2024-01-16 15:21:35 +00:00
|
|
|
s := p.ctrl.GetCurrentSong()
|
|
|
|
objPath := mprisNoTrack
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
if s != nil {
|
2024-12-18 15:29:16 +00:00
|
|
|
objPath = encodeTrackID(s.ID)
|
2024-01-16 15:21:35 +00:00
|
|
|
} else {
|
|
|
|
s = &subsonic.Child{}
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
|
|
|
md := mprisTypes.Metadata{
|
2024-01-16 15:21:35 +00:00
|
|
|
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)
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
if artw != nil {
|
2024-12-18 15:29:16 +00:00
|
|
|
md.ArtUrl = "file://" + *artw
|
2024-01-16 15:21:35 +00:00
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return md, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Volume() (float64, error) {
|
|
|
|
return 1, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) SetVolume(float64) error {
|
|
|
|
return nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) Position() (int64, error) {
|
|
|
|
return int64(secondsToMicroseconds(int(p.ctrl.position))), nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) MinimumRate() (float64, error) {
|
|
|
|
return 1, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) MaximumRate() (float64, error) {
|
|
|
|
return 1, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) CanGoNext() (bool, error) {
|
|
|
|
return p.ctrl.queue.HasNext(), nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) CanGoPrevious() (bool, error) {
|
|
|
|
return p.ctrl.queue.HasPrev(), nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) CanPlay() (bool, error) {
|
|
|
|
return p.ctrl.GetCurrentSong() != nil, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) CanPause() (bool, error) {
|
|
|
|
return true, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p mprisPlayer) CanSeek() (bool, error) {
|
|
|
|
return false, nil
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
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
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
p.err = p.eventHandler.Player.OnPlayPause()
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p *mprisPlayback) OnPlaylistChanged() {
|
|
|
|
if p.err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
p.err = p.eventHandler.Player.OnOptions()
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
func (p *mprisPlayback) OnSongChanged() {
|
|
|
|
if p.err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
p.err = p.eventHandler.Player.OnTitle()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *mprisPlayback) OnPositionChanged(position int) {
|
|
|
|
if p.err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
p.err = p.eventHandler.Player.OnSeek(secondsToMicroseconds(position))
|
|
|
|
if p.err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-18 15:29:16 +00:00
|
|
|
func secondsToMicroseconds(s int) mprisTypes.Microseconds {
|
|
|
|
return mprisTypes.Microseconds(s * 1_000_000)
|
2024-01-16 15:21:35 +00:00
|
|
|
}
|
|
|
|
|
2024-12-18 15:29:16 +00:00
|
|
|
func encodeTrackID(id string) string {
|
2024-01-16 15:21:35 +00:00
|
|
|
data := []byte(id)
|
2024-12-18 15:29:16 +00:00
|
|
|
|
2024-01-16 15:21:35 +00:00
|
|
|
return fmt.Sprintf("/%s/Track/%s", mprisPlayerNmae, base32.StdEncoding.WithPadding('0').EncodeToString(data))
|
|
|
|
}
|