1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
|
package mpris
import (
"fmt"
"strings"
"github.com/godbus/dbus/v5"
)
const (
dbusObjectPath = "/org/mpris/MediaPlayer2"
propertiesChangedSignal = "org.freedesktop.DBus.Properties.PropertiesChanged"
BaseInterface = "org.mpris.MediaPlayer2"
PlayerInterface = "org.mpris.MediaPlayer2.Player"
TrackListInterface = "org.mpris.MediaPlayer2.TrackList"
PlaylistsInterface = "org.mpris.MediaPlayer2.Playlists"
getPropertyMethod = "org.freedesktop.DBus.Properties.Get"
setPropertyMethod = "org.freedesktop.DBus.Properties.Set"
)
func getProperty(obj *dbus.Object, iface string, prop string) (dbus.Variant, error) {
result := dbus.Variant{}
err := obj.Call(getPropertyMethod, 0, iface, prop).Store(&result)
if err != nil {
return dbus.Variant{}, err
}
return result, nil
}
func setProperty(obj *dbus.Object, iface string, prop string, val interface{}) error {
call := obj.Call(setPropertyMethod, 0, iface, prop, dbus.MakeVariant(val))
return call.Err
}
func convertToMicroseconds(seconds float64) int64 {
return int64(seconds * 1000000)
}
func convertToSeconds(microseconds int64) float64 {
return float64(microseconds) / 1000000.0
}
// List lists the available players.
func List(conn *dbus.Conn) ([]string, error) {
var names []string
err := conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names)
if err != nil {
return nil, err
}
var mprisNames []string
for _, name := range names {
if strings.HasPrefix(name, BaseInterface) {
mprisNames = append(mprisNames, name)
}
}
return mprisNames, nil
}
// Player represents a mpris player.
type Player struct {
conn *dbus.Conn
obj *dbus.Object
name string
}
// Raise raises player priority.
func (i *Player) Raise() error {
return i.obj.Call(BaseInterface+".Raise", 0).Err
}
// Quit closes the player.
func (i *Player) Quit() error {
return i.obj.Call(BaseInterface+".Quit", 0).Err
}
// GetIdentity returns the player identity.
func (i *Player) GetIdentity() (string, error) {
value, err := getProperty(i.obj, BaseInterface, "Identity")
return value.Value().(string), err
}
// Next skips to the next track in the tracklist.
func (i *Player) Next() error {
return i.obj.Call(PlayerInterface+".Next", 0).Err
}
// Previous skips to the previous track in the tracklist.
func (i *Player) Previous() error {
return i.obj.Call(PlayerInterface+".Previous", 0).Err
}
// Pause pauses the current track.
func (i *Player) Pause() error {
return i.obj.Call(PlayerInterface+".Pause", 0).Err
}
// PlayPause resumes the current track if it's paused and pauses it if it's playing.
func (i *Player) PlayPause() error {
return i.obj.Call(PlayerInterface+".PlayPause", 0).Err
}
// Stop stops the current track.
func (i *Player) Stop() error {
return i.obj.Call(PlayerInterface+".Stop", 0).Err
}
// Play starts or resumes the current track.
func (i *Player) Play() error {
return i.obj.Call(PlayerInterface+".Play", 0).Err
}
// Seek seeks the current track position by the offset. The offset should be in seconds.
// If the offset is negative it's seeked back.
func (i *Player) Seek(offset float64) error {
return i.obj.Call(PlayerInterface+".Seek", 0, convertToMicroseconds(offset)).Err
}
// SetTrackPosition sets the position of a track. The position should be in seconds.
func (i *Player) SetTrackPosition(trackId *dbus.ObjectPath, position float64) error {
return i.obj.Call(PlayerInterface+".SetPosition", 0, trackId, convertToMicroseconds(position)).Err
}
// OpenUri opens and plays the uri if supported.
func (i *Player) OpenUri(uri string) error {
return i.obj.Call(PlayerInterface+".OpenUri", 0, uri).Err
}
// PlaybackStatus the status of the playback. It can be "Playing", "Paused" or "Stopped".
type PlaybackStatus string
const (
PlaybackPlaying PlaybackStatus = "Playing"
PlaybackPaused PlaybackStatus = "Paused"
PlaybackStopped PlaybackStatus = "Stopped"
)
// GetPlaybackStatus gets the playback status.
func (i *Player) GetPlaybackStatus() (PlaybackStatus, error) {
variant, err := i.obj.GetProperty(PlayerInterface + ".PlaybackStatus")
if err != nil {
return "", err
}
if variant.Value() == nil {
return "", fmt.Errorf("Variant value is nil")
}
return PlaybackStatus(variant.Value().(string)), nil
}
// LoopStatus the status of the player loop. It can be "None", "Track" or "Playlist".
type LoopStatus string
const (
LoopNone LoopStatus = "None"
LoopTrack LoopStatus = "Track"
LoopPlaylist LoopStatus = "Playlist"
)
// GetLoopStatus returns the loop status.
func (i *Player) GetLoopStatus() (LoopStatus, error) {
variant, err := getProperty(i.obj, PlayerInterface, "LoopStatus")
if err != nil {
return LoopStatus(""), err
}
if variant.Value() == nil {
return "", fmt.Errorf("Variant value is nil")
}
return LoopStatus(variant.Value().(string)), nil
}
// SetLoopStatus sets the loop status to loopStatus.
func (i *Player) SetLoopStatus(loopStatus LoopStatus) error {
return i.SetPlayerProperty("LoopStatus", loopStatus)
}
// SetProperty sets the value of a propertyName in the targetInterface.
func (i *Player) SetProperty(targetInterface, propertyName string, value interface{}) error {
return setProperty(i.obj, targetInterface, propertyName, value)
}
// SetPlayerProperty sets the propertyName from the player interface.
func (i *Player) SetPlayerProperty(propertyName string, value interface{}) error {
return setProperty(i.obj, PlayerInterface, propertyName, value)
}
// GetProperty returns the properityName in the targetInterface.
func (i *Player) GetProperty(targetInterface, properityName string) (dbus.Variant, error) {
return getProperty(i.obj, targetInterface, properityName)
}
// GetPlayerProperty returns the properityName from the player interface.
func (i *Player) GetPlayerProperty(properityName string) (dbus.Variant, error) {
return getProperty(i.obj, PlayerInterface, properityName)
}
// Returns the current playback rate.
func (i *Player) GetRate() (float64, error) {
variant, err := getProperty(i.obj, PlayerInterface, "Rate")
if err != nil {
return 0.0, err
}
if variant.Value() == nil {
return 0.0, fmt.Errorf("Variant value is nil")
}
return variant.Value().(float64), nil
}
// GetShuffle returns false if the player is going linearly through a playlist and false if it's
// in some other order.
func (i *Player) GetShuffle() (bool, error) {
variant, err := getProperty(i.obj, PlayerInterface, "Shuffle")
if err != nil {
return false, err
}
if variant.Value() == nil {
return false, fmt.Errorf("Variant value is nil")
}
return variant.Value().(bool), nil
}
// SetShuffle sets the shuffle playlist mode.
func (i *Player) SetShuffle(value bool) error {
return setProperty(i.obj, PlayerInterface, "Shuffle", value)
}
// GetMetadata returns the metadata.
func (i *Player) GetMetadata() (map[string]dbus.Variant, error) {
variant, err := getProperty(i.obj, PlayerInterface, "Metadata")
if err != nil {
return nil, err
}
if variant.Value() == nil {
return nil, fmt.Errorf("Variant value is nil")
}
return variant.Value().(map[string]dbus.Variant), nil
}
// GetVolume returns the volume.
func (i *Player) GetVolume() (float64, error) {
variant, err := getProperty(i.obj, PlayerInterface, "Volume")
if err != nil {
return 0.0, err
}
if variant.Value() == nil {
return 0.0, fmt.Errorf("Variant value is nil")
}
return variant.Value().(float64), nil
}
// SetVolume sets the volume.
func (i *Player) SetVolume(volume float64) error {
return setProperty(i.obj, PlayerInterface, "Volume", volume)
}
// GetLength returns the current track length in seconds.
func (i *Player) GetLength() (float64, error) {
metadata, err := i.GetMetadata()
if err != nil {
return 0.0, err
}
if metadata == nil || metadata["mpris:length"].Value() == nil {
return 0.0, fmt.Errorf("Variant value is nil")
}
return convertToSeconds(metadata["mpris:length"].Value().(int64)), nil
}
// GetPosition returns the position in seconds of the current track.
func (i *Player) GetPosition() (float64, error) {
variant, err := getProperty(i.obj, PlayerInterface, "Position")
if err != nil {
return 0.0, err
}
if variant.Value() == nil {
return 0.0, fmt.Errorf("Variant value is nil")
}
return convertToSeconds(variant.Value().(int64)), nil
}
// SetPosition sets the position of the current track. The position should be in seconds.
func (i *Player) SetPosition(position float64) error {
metadata, err := i.GetMetadata()
if err != nil {
return err
}
if metadata == nil || metadata["mpris:trackid"].Value() == nil {
return fmt.Errorf("Variant value is nil")
}
trackId := metadata["mpris:trackid"].Value().(dbus.ObjectPath)
i.SetTrackPosition(&trackId, position)
return nil
}
// New connects the the player with the name in the connection conn.
func New(conn *dbus.Conn, name string) *Player {
obj := conn.Object(name, dbusObjectPath).(*dbus.Object)
return &Player{conn, obj, name}
}
// OnSignal adds a handler to the player's properties change signal.
func (i *Player) OnSignal(ch chan<- *dbus.Signal) (err error) {
err = i.conn.AddMatchSignal(
dbus.WithMatchSender(i.name),
dbus.WithMatchObjectPath(i.obj.Path()),
dbus.WithMatchInterface("org.freedesktop.DBus.Properties"),
)
if err != nil {
return
}
i.conn.Signal(ch)
return
}
|