[DBus (name = "org.mpris.MediaPlayer2")] class Mpris : Object { internal signal void on_raise (); internal signal void on_quit (); public bool can_raise { get { return true; } } public void raise () throws Error { this.on_raise (); } public bool can_quit { get { return true; } } public void quit () throws Error { this.on_quit (); } public bool can_set_fullscreen { get { return false; } } public bool fullscreen { get { return false; } set { assert (false); } } public bool has_track_list { get { return false; } } public string identity { owned get { return "audrey"; } } public string desktop_entry { owned get { return "eu.callcc.audrey"; } } public string[] supported_uri_schemes { owned get { return {}; } } public string[] supported_mime_types { owned get { return {}; } } internal Mpris (Ui.Window window) { this.on_raise.connect (() => window.present ()); this.on_quit.connect (() => window.close ()); } ~Mpris () { debug ("destroying mpris"); } } [DBus (name = "org.mpris.MediaPlayer2.Player")] class MprisPlayer : Object { internal signal void on_next (); internal signal void on_previous (); internal signal void on_pause (); internal signal void on_play_pause (); internal signal void on_stop (); internal signal void on_play (); internal signal void on_seek (int64 offset); internal signal void on_set_position (ObjectPath track_id, int64 position); public void next () throws Error { this.on_next (); } public void previous () throws Error { this.on_previous (); } public void pause () throws Error { print("pause\n");this.on_pause (); } public void play_pause () throws Error { this.on_play_pause (); } public void stop () throws Error { this.on_stop (); } public void play () throws Error { this.on_play (); } public void seek (int64 offset) throws Error { this.on_seek (offset); } public void set_position (ObjectPath track_id, int64 position) throws Error { this.on_set_position (track_id, position); } public void open_uri (string uri) throws Error { assert (false); } public signal void seeked (int64 position); public string playback_status { owned get; internal set; default = "Stopped"; } public string loop_status { owned get; /*set;*/ default = "None"; } public double rate { get; /*set*/ default = 1.0; } public bool shuffle { get; /*set*/ default = false; } public HashTable metadata { owned get; private set; default = new HashTable (null, null); } public double volume { get; set; default = 1.0; } [CCode (notify = false)] public int64 position { get; default = 0; } public double minimum_rate { get { return 1.0; } } public double maximum_rate { get { return 1.0; } } public bool can_go_next { get; private set; default = false; } public bool can_go_previous { get; private set; default = false; } public bool can_play { get; private set; default = false; } public bool can_pause { get; private set; default = false; } public bool can_seek { get; private set; default = false; } [CCode (notify = false)] public bool can_control { get { return true; } } internal Subsonic.Client api { get; set; } internal MprisPlayer (DBusConnection conn, Playbin playbin) { playbin.bind_property ( "state", this, "playback_status", BindingFlags.DEFAULT, (binding, from, ref to) => { switch (from.get_enum ()) { case PlaybinState.STOPPED: to.set_string ("Stopped"); this.can_go_next = false; this.can_go_previous = false; this.can_play = false; this.can_pause = false; this.can_seek = false; return true; case PlaybinState.PAUSED: to.set_string ("Paused"); this.can_go_next = true; this.can_go_previous = true; this.can_play = true; this.can_pause = true; this.can_seek = true; return true; case PlaybinState.PLAYING: to.set_string ("Playing"); this.can_go_next = true; this.can_go_previous = true; this.can_play = true; this.can_pause = true; this.can_seek = true; return true; } return false; }); playbin.bind_property ( "volume", this, "volume", BindingFlags.BIDIRECTIONAL, (binding, from, ref to) => { to.set_double (from.get_int () / 100.0); return true; }, (binding, from, ref to) => { to.set_int ((int) (from.get_double () * 100.0)); return true; }); playbin.new_track.connect ((playbin) => { PlaybinSong song = (PlaybinSong) playbin.play_queue.get_item (playbin.play_queue_position); var metadata = new HashTable (null, null); metadata["mpris:trackid"] = new ObjectPath (@"/eu/callcc/audrey/track/$(song.id)"); metadata["mpris:length"] = (int64) song.duration * 1000000; if (this.api != null) metadata["mpris:artUrl"] = this.api.cover_art_uri (song.id); metadata["xesam:album"] = song.album; metadata["xesam:artist"] = new string[] {song.artist}; if (song.genre != null) metadata["xesam:genre"] = song.genre; metadata["xesam:title"] = song.title; metadata["xesam:trackNumber"] = song.track; metadata["xesam:useCount"] = song.play_count; // TODO metadata["xesam:userRating"] = song.starred != null ? 1.0 : 0.0; this.metadata = metadata; }); playbin.stopped.connect (() => { this.metadata = new HashTable (null, null); }); this.on_next.connect (() => playbin.go_to_next_track ()); this.on_previous.connect (() => playbin.go_to_prev_track ()); this.on_play.connect (() => playbin.play ()); this.on_pause.connect (() => playbin.pause ()); this.on_play_pause.connect (() => { if (playbin.state == PlaybinState.PAUSED) playbin.play (); else if (playbin.state == PlaybinState.PLAYING) playbin.pause (); }); this.on_stop.connect (() => { playbin.stop (); }); // TODO: seeking from mpris // TODO: trigger the seeked signal when applicable this.notify.connect ((p) => { var builder = new VariantBuilder (VariantType.ARRAY); var invalid_builder = new VariantBuilder (new VariantType ("as")); string dbus_name; Variant dbus_value; switch (p.name) { case "playback-status": dbus_name = "PlaybackStatus"; dbus_value = this.playback_status; break; case "loop-status": dbus_name = "LoopStatus"; dbus_value = this.loop_status; break; case "rate": dbus_name = "Rate"; dbus_value = this.rate; break; case "shuffle": dbus_name = "Shuffle"; dbus_value = this.shuffle; break; case "metadata": dbus_name = "Metadata"; dbus_value = this.metadata; break; case "volume": dbus_name = "Volume"; dbus_value = this.volume; break; case "minimum-rate": dbus_name = "MinimumRate"; dbus_value = this.minimum_rate; break; case "maximum-rate": dbus_name = "MaximumRate"; dbus_value = this.maximum_rate; break; case "can-go-next": dbus_name = "CanGoNext"; dbus_value = this.can_go_next; break; case "can-go-previous": dbus_name = "CanGoPrevious"; dbus_value = this.can_go_previous; break; case "can-play": dbus_name = "CanPlay"; dbus_value = this.can_play; break; case "can-pause": dbus_name = "CanPause"; dbus_value = this.can_pause; break; case "can-seek": dbus_name = "CanSeek"; dbus_value = this.can_seek; break; case "api": // internal, ignored return; default: warning (@"unknown mpris player property $(p.name)"); return; } builder.add ("{sv}", dbus_name, dbus_value); try { conn.emit_signal ( null, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "PropertiesChanged", new Variant ( "(sa{sv}as)", "org.mpris.MediaPlayer2.Player", builder, invalid_builder)); } catch (Error e) { error (@"could not notify of mpris property changes: $(e.message)"); } }); } ~MprisPlayer () { debug ("destroying mpris player"); } }