music_track.py 6.55 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com>
#                    Nikos Kouremenos <kourem AT gmail.com>
# Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
#                    Jonathan Schleifer <js-gajim AT webkeks.org>
#                    Stephan Erb <steve-e AT h3c.de>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
Tanguy Ortolo's avatar
Tanguy Ortolo committed
21

22 23
import logging

24
from gi.repository import GObject
25 26
from gi.repository import Gio, GLib

Martin's avatar
Martin committed
27
log = logging.getLogger('gajim.c.dbus.music_track')
28

29
MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.'
30 31


32
class MusicTrackInfo:
Tanguy Ortolo's avatar
Tanguy Ortolo committed
33
    __slots__ = ['title', 'album', 'artist', 'duration', 'track_number',
34 35
                 'paused']

Tanguy Ortolo's avatar
Tanguy Ortolo committed
36

37
class MusicTrackListener(GObject.GObject):
Tanguy Ortolo's avatar
Tanguy Ortolo committed
38
    __gsignals__ = {
39
        'music-track-changed': (GObject.SignalFlags.RUN_LAST, None, (object,)),
Tanguy Ortolo's avatar
Tanguy Ortolo committed
40 41 42
    }

    _instance = None
43

Tanguy Ortolo's avatar
Tanguy Ortolo committed
44 45 46 47 48 49 50
    @classmethod
    def get(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def __init__(self):
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
        super().__init__()
        self.players = {}

    def start(self):
        proxy = Gio.DBusProxy.new_for_bus_sync(
            Gio.BusType.SESSION,
            Gio.DBusProxyFlags.NONE,
            None,
            'org.freedesktop.DBus',
            '/org/freedesktop/DBus',
            'org.freedesktop.DBus',
            None)

        self.connection = proxy.get_connection()
        self.connection.signal_subscribe(
            'org.freedesktop.DBus',
            'org.freedesktop.DBus',
            'NameOwnerChanged',
            '/org/freedesktop/DBus',
            None,
            Gio.DBusSignalFlags.NONE,
            self._signal_name_owner_changed)

        try:
            result = proxy.call_sync(
                'ListNames',
                None,
                Gio.DBusCallFlags.NONE,
                -1,
                None)
        except GLib.Error as error:
            if error.domain == 'g-dbus-error-quark':
                log.debug("Could not list names: %s", error.message)
                return
            raise

        for name in result[0]:
            if name.startswith(MPRIS_PLAYER_PREFIX):
                self._add_player(name)

    def stop(self):
        for name in list(self.players):
            if name.startswith(MPRIS_PLAYER_PREFIX):
                self._remove_player(name)

    def _signal_name_owner_changed(self, connection, sender_name, object_path,
                         interface_name, signal_name, parameters, *user_data):
        name, oldOwner, newOwner = parameters
        if name.startswith(MPRIS_PLAYER_PREFIX):
            if newOwner and not oldOwner:
                self._add_player(name)
            else:
                self._remove_player(name)

    def _add_player(self, name):
106 107
        '''Set up a listener for music player signals'''
        log.info('%s appeared', name)
108 109 110 111 112

        if name in self.players:
            return

        self.players[name] = self.connection.signal_subscribe(
113 114 115 116 117 118 119 120 121 122 123 124
            name,
            'org.freedesktop.DBus.Properties',
            'PropertiesChanged',
            '/org/mpris/MediaPlayer2',
            None,
            Gio.DBusSignalFlags.NONE,
            self._signal_received,
            name)

        info = self.get_playing_track(name)
        if info is not None:
            self.emit('music-track-changed', info)
Tanguy Ortolo's avatar
Tanguy Ortolo committed
125

126
    def _remove_player(self, name):
127
        log.info('%s vanished', name)
128 129 130 131
        if name in self.players:
            self.connection.signal_unsubscribe(
                self.players[name])
            self.players.pop(name)
Tanguy Ortolo's avatar
Tanguy Ortolo committed
132 133 134

            self.emit('music-track-changed', None)

135 136 137
    def _signal_received(self, connection, sender_name, object_path,
                         interface_name, signal_name, parameters, *user_data):
        '''Signal handler for PropertiesChanged event'''
Tanguy Ortolo's avatar
Tanguy Ortolo committed
138

139 140
        if 'PlaybackStatus' not in parameters[1]:
            return
Tanguy Ortolo's avatar
Tanguy Ortolo committed
141

142
        log.info('Signal received: %s - %s', interface_name, parameters)
Tanguy Ortolo's avatar
Tanguy Ortolo committed
143

144
        info = self.get_playing_track(user_data[0])
Tanguy Ortolo's avatar
Tanguy Ortolo committed
145 146 147

        self.emit('music-track-changed', info)

148 149 150 151
    def _properties_extract(self, properties):
        meta = properties.get('Metadata')
        if meta is None or not meta:
            return None
Tanguy Ortolo's avatar
Tanguy Ortolo committed
152 153

        info = MusicTrackInfo()
154 155 156
        info.title = meta.get('xesam:title')
        info.album = meta.get('xesam:album')
        artist = meta.get('xesam:artist')
157
        if artist:
158
            info.artist = artist[0]
Tanguy Ortolo's avatar
Tanguy Ortolo committed
159
        else:
160 161 162 163 164 165
            info.artist = None
        info.duration = float(meta.get('mpris:length', 0))
        info.track_number = meta.get('xesam:trackNumber', 0)

        status = properties.get('PlaybackStatus')
        info.paused = status is not None and status == 'Paused'
Tanguy Ortolo's avatar
Tanguy Ortolo committed
166 167 168

        return info

169
    def get_playing_track(self, name):
Tanguy Ortolo's avatar
Tanguy Ortolo committed
170 171
        '''Return a MusicTrackInfo for the currently playing
        song, or None if no song is playing'''
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
        proxy = Gio.DBusProxy.new_for_bus_sync(
            Gio.BusType.SESSION,
            Gio.DBusProxyFlags.NONE,
            None,
            name,
            '/org/mpris/MediaPlayer2',
            'org.freedesktop.DBus.Properties',
            None)

        try:
            result = proxy.call_sync(
                "GetAll",
                GLib.Variant('(s)', ('org.mpris.MediaPlayer2.Player',)),
                Gio.DBusCallFlags.NONE,
                -1,
                None)
188 189 190
        except GLib.Error as error:
            if error.domain == 'g-dbus-error-quark':
                log.debug("Could not enable music listener: %s", error.message)
Tanguy Ortolo's avatar
Tanguy Ortolo committed
191
                return None
192
            raise
193 194
        else:
            info = self._properties_extract(result[0])
Tanguy Ortolo's avatar
Tanguy Ortolo committed
195 196
            return info

Tanguy Ortolo's avatar
Tanguy Ortolo committed
197 198 199

# here we test :)
if __name__ == '__main__':
200
    def music_track_change_cb(_listener, music_track_info):
201
        if music_track_info is None or music_track_info.paused:
202
            print('Stop!')
Tanguy Ortolo's avatar
Tanguy Ortolo committed
203
        else:
204
            print(music_track_info.title)
Tanguy Ortolo's avatar
Tanguy Ortolo committed
205 206
    listener = MusicTrackListener.get()
    listener.connect('music-track-changed', music_track_change_cb)
207 208
    listener.start()
    GLib.MainLoop().run()