Merge branch 'master' into gtk3

This commit is contained in:
Eric Le Lay 2017-04-23 13:44:56 +02:00
commit 35870290ed
3 changed files with 158 additions and 7 deletions

View File

@ -13,8 +13,8 @@ logger = logging.getLogger(__name__)
_ = gpodder.gettext
__title__ = _('Enqueue in media players')
__description__ = _('Add a context menu item for enqueueing episodes in installed media players')
__title__ = _('Enqueue/Resume in media players')
__description__ = _('Add a context menu item for enqueueing/resuming playback of episodes in installed media players')
__authors__ = 'Thomas Perl <thp@gpodder.org>, Bernd Schlapsi <brot@gmx.info>'
__doc__ = 'http://wiki.gpodder.org/wiki/Extensions/EnqueueInMediaplayer'
__payment__ = 'https://flattr.com/submit/auto?user_id=BerndSch&url=http://wiki.gpodder.org/wiki/Extensions/EnqueueInMediaplayer'
@ -23,8 +23,8 @@ __only_for__ = 'gtk'
DefaultConfig = {
'enqueue_after_download': False, # Set to True to enqueue an episode right after downloading
'default_player': '', # Set to the player to be used for auto-enqueueing (otherwise pick first installed)
'enqueue_after_download': False, # Set to True to enqueue an episode right after downloading
'default_player': '', # Set to the player to be used for auto-enqueueing (otherwise pick first installed)
}
@ -59,7 +59,7 @@ class FreeDesktopPlayer(Player):
def open_files(self, filenames):
subprocess.Popen(self.command + filenames,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
class Win32Player(Player):
@ -81,6 +81,115 @@ class Win32Player(Player):
subprocess.Popen(cmd)
class MPRISResumer(FreeDesktopPlayer):
"""
resume episod playback at saved time
"""
OBJECT_PLAYER = '/org/mpris/MediaPlayer2'
OBJECT_DBUS = '/org/freedesktop/DBus'
INTERFACE_PLAYER = 'org.mpris.MediaPlayer2.Player'
INTERFACE_PROPS = 'org.freedesktop.DBus.Properties'
SIGNAL_PROP_CHANGE = 'PropertiesChanged'
NAME_DBUS = 'org.freedesktop.DBus'
def __init__(self, slug, application, command, bus_name):
super(MPRISResumer, self).__init__(slug, application, command)
self.title = '/'.join((_('Resume in'), application))
self.bus_name = bus_name
self.player = None
self.position_us = None
self.url = None
def is_installed(self):
if gpodder.ui.win32:
return False
return util.find_command(self.command[0]) is not None
def enqueue_episodes(self, episodes):
self.do_enqueue(episodes[0].get_playback_url(),
episodes[0].current_position)
for episode in episodes:
episode.playback_mark()
if self.gpodder is not None:
self.gpodder.update_episode_list_icons(selected=True)
def init_dbus(self):
bus = gpodder.dbus_session_bus
if not bus.name_has_owner(self.bus_name):
logger.debug('MPRISResumer %s is not there...', self.bus_name)
return False
self.player = bus.get_object(self.bus_name, self.OBJECT_PLAYER)
self.signal_match = self.player.connect_to_signal(self.SIGNAL_PROP_CHANGE,
self.on_prop_change,
dbus_interface=self.INTERFACE_PROPS)
return True
def enqueue_when_ready(self, filename, pos):
def name_owner_changed(name, old_owner, new_owner):
logger.debug('name_owner_changed "%s" "%s" "%s"',
name, old_owner, new_owner)
if name == self.bus_name:
logger.debug('MPRISResumer player %s is there', name)
cancel.remove()
util.idle_add(lambda: self.do_enqueue(filename, pos))
bus = gpodder.dbus_session_bus
obj = bus.get_object(self.NAME_DBUS, self.OBJECT_DBUS)
cancel = obj.connect_to_signal('NameOwnerChanged', name_owner_changed, dbus_interface=self.NAME_DBUS)
def do_enqueue(self, filename, pos):
def on_reply():
logger.debug('MPRISResumer opened %s', self.url)
def on_error(exception):
logger.error('MPRISResumer error %s', repr(exception))
self.signal_match.remove()
if filename.startswith('/'):
try:
import pathlib
self.url = pathlib.Path(filename).as_uri()
except ImportError:
self.url = 'file://' + filename
self.position_us = pos * 1000 * 1000 # pos in microseconds
if self.init_dbus():
# async to not freeze the ui waiting for the application to answer
self.player.OpenUri(self.url,
dbus_interface=self.INTERFACE_PLAYER,
reply_handler=on_reply,
error_handler=on_error)
else:
self.enqueue_when_ready(filename, pos)
logger.debug('MPRISResumer launching player %s', self.application)
super(MPRISResumer, self).open_files([])
def on_prop_change(self, interface, props, invalidated_props):
def on_reply():
pass
def on_error(exception):
logger.error('MPRISResumer SetPosition error %s', repr(exception))
self.signal_match.remove()
metadata = props.get('Metadata', {})
url = metadata.get('xesam:url')
track_id = metadata.get('mpris:trackid')
if url is not None and track_id is not None:
if url == self.url:
logger.info('Enqueue %s setting track %s position=%d',
url, track_id, self.position_us)
self.player.SetPosition(str(track_id), self.position_us,
dbus_interface=self.INTERFACE_PLAYER,
reply_handler=on_reply,
error_handler=on_error)
else:
logger.debug('Changed but wrong url: %s, giving up', url)
self.signal_match.remove()
PLAYERS = [
# Amarok, http://amarok.kde.org/
FreeDesktopPlayer('amarok', 'Amarok', ['amarok', '--play', '--append']),
@ -116,6 +225,37 @@ PLAYERS = [
Win32Player('foobar2000', 'foobar2000', r'HKEY_CLASSES_ROOT\foobar2000.MP3\shell\enqueue\command'),
]
RESUMERS = [
# doesn't play on my system, but the track is appended.
MPRISResumer('amarok', 'Amarok', ['amarok', '--play'], 'org.mpris.MediaPlayer2.amarok'),
MPRISResumer('vlc', 'VLC', ['vlc', '--started-from-file'], 'org.mpris.MediaPlayer2.vlc'),
# totem mpris2 plugin is broken for me: it raises AttributeError:
# File "/usr/lib/totem/plugins/dbus/dbusservice.py", line 329, in OpenUri
# self.totem.add_to_playlist_and_play (uri)
# MPRISResumer('totem', 'Totem', ['totem'], 'org.mpris.MediaPlayer2.totem'),
# with https://github.com/Serranya/deadbeef-mpris2-plugin
MPRISResumer('resume in deadbeef', 'DeaDBeeF', ['deadbeef'], 'org.mpris.MediaPlayer2.DeaDBeeF'),
# the gPodder Dowloads directory must be in gmusicbrowser's library
MPRISResumer('resume in gmusicbrowser', 'gmusicbrowser', ['gmusicbrowser'], 'org.mpris.MediaPlayer2.gmusicbrowser'),
# Audacious doesn't implement MPRIS2.OpenUri
# MPRISResumer('audacious', 'resume in Audacious', ['audacious', '--enqueue'], 'org.mpris.MediaPlayer2.audacious'),
# beware: clementine never exits on my system (even when launched from cmdline)
# so the zombie clementine process will get all the bus messages and never answer
# resulting in freezes and timeouts!
MPRISResumer('clementine', 'Clementine', ['clementine'], 'org.mpris.MediaPlayer2.clementine'),
# just enable the plugin
MPRISResumer('parole', 'Parole', ['parole'], 'org.mpris.MediaPlayer2.parole'),
]
class gPodderExtension:
def __init__(self, container):
self.container = container
@ -123,17 +263,26 @@ class gPodderExtension:
# Only display media players that can be found at extension load time
self.players = [player for player in PLAYERS if player.is_installed()]
self.resumers = [r for r in RESUMERS if r.is_installed()]
def on_ui_object_available(self, name, ui_object):
if name == 'gpodder-gtk':
for p in self.players:
for p in self.players + self.resumers:
p.gpodder = ui_object
def on_episodes_context_menu(self, episodes):
if not any(e.file_exists() for e in episodes):
return None
return [(p.title, p.enqueue_episodes) for p in self.players]
ret = [(p.title, p.enqueue_episodes) for p in self.players]
# needs dbus, doesn't handle more than 1 episode
# and no point in using DBus when episode is not played.
if not hasattr(gpodder.dbus_session_bus, 'fake') and \
len(episodes) == 1 and episodes[0].current_position > 0:
ret.extend([(p.title, p.enqueue_episodes) for p in self.resumers])
return ret
def on_episode_downloaded(self, episode):
if self.config.enqueue_after_download:

View File

@ -285,6 +285,7 @@ class MPRISDBusReceiver(object):
class gPodderNotifier(dbus.service.Object):
def __init__(self, bus, path):
dbus.service.Object.__init__(self, bus, path)
self.start_position = 0
@dbus.service.signal(dbus_interface='org.gpodder.player', signature='us')
def PlaybackStarted(self, start_position, file_uri):

View File

@ -2,6 +2,7 @@ import dbus.exceptions
class SessionBus(object):
def __init__(self, *args, **kwargs):
self.fake = True
pass
def add_signal_receiver(self, *args, **kwargs):