250 lines
5.5 KiB
Go
250 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))
|
||
|
}
|