First cut of Media Player D-Bus API support
This makes gPodder depend on mygpoclient >= 1.4. For players like Panucci, this will save the current playtime and the total time of the podcast episode in the local database and also send exact played events to the web server.
This commit is contained in:
parent
5b268229eb
commit
20b9204613
2
README
2
README
|
@ -32,7 +32,7 @@
|
|||
* python (>= 2.5) with sqlite3 support
|
||||
* python-gtk2 (>= 2.12)
|
||||
* python-feedparser
|
||||
* python-mygpoclient (>= 1.2; http://thpinfo.com/2010/mygpoclient/)
|
||||
* python-mygpoclient (>= 1.4; http://thpinfo.com/2010/mygpoclient/)
|
||||
* python-dbus (optional, but highly recommended)
|
||||
|
||||
If your Python installation does not come with "sqlite3" support,
|
||||
|
|
|
@ -320,6 +320,9 @@ class EpisodeListModel(gtk.ListStore):
|
|||
if show_padlock:
|
||||
tooltip.append(_('deletion prevented'))
|
||||
|
||||
if episode.total_time > 0:
|
||||
tooltip.append('%d%%' % (100.*float(episode.current_position)/float(episode.total_time)))
|
||||
|
||||
tooltip = ', '.join(tooltip)
|
||||
|
||||
if status_icon is not None:
|
||||
|
|
|
@ -71,6 +71,7 @@ from gpodder import opml
|
|||
from gpodder import download
|
||||
from gpodder import my
|
||||
from gpodder import youtube
|
||||
from gpodder import player
|
||||
from gpodder.liblogger import log
|
||||
|
||||
_ = gpodder.gettext
|
||||
|
@ -230,6 +231,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
self.config.connect_gtk_paned('paned_position', self.channelPaned)
|
||||
self.main_window.show()
|
||||
|
||||
self.player_receiver = player.MediaPlayerDBusReceiver(self.on_played)
|
||||
|
||||
self.gPodder.connect('key-press-event', self.on_key_press)
|
||||
|
||||
self.preferences_dialog = None
|
||||
|
@ -536,6 +539,40 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
if not self.channels and not gpodder.ui.fremantle:
|
||||
util.idle_add(self.on_itemUpdate_activate)
|
||||
|
||||
def on_played(self, start, end, total, file_uri):
|
||||
"""Handle the "played" signal from a media player"""
|
||||
log('Received play action: %s (%d, %d, %d)', file_uri, start, end, total, sender=self)
|
||||
filename = file_uri[len('file://'):]
|
||||
# FIXME: Optimize this by querying the database more directly
|
||||
for channel in self.channels:
|
||||
for episode in channel.get_all_episodes():
|
||||
fn = episode.local_filename(create=False, check_only=True)
|
||||
if fn == filename:
|
||||
file_type = episode.file_type()
|
||||
# Automatically enable D-Bus played status mode
|
||||
if file_type == 'audio':
|
||||
self.config.audio_played_dbus = True
|
||||
elif file_type == 'video':
|
||||
self.config.video_played_dbus = True
|
||||
|
||||
now = time.time()
|
||||
if total > 0:
|
||||
episode.total_time = total
|
||||
if episode.current_position_updated is None or \
|
||||
now > episode.current_position_updated:
|
||||
episode.current_position = end
|
||||
episode.current_position_updated = now
|
||||
episode.mark(is_played=True)
|
||||
episode.save()
|
||||
self.db.commit()
|
||||
self.update_episode_list_icons([episode.url])
|
||||
self.update_podcast_list_model([episode.channel.url])
|
||||
|
||||
# Submit this action to the webservice
|
||||
self.mygpo_client.on_playback_full(episode, \
|
||||
start, end, total)
|
||||
return
|
||||
|
||||
def on_add_remove_podcasts_mygpo(self):
|
||||
actions = self.mygpo_client.get_received_actions()
|
||||
if not actions:
|
||||
|
@ -1266,6 +1303,10 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
def _on_config_changed(self, name, old_value, new_value):
|
||||
if name == 'show_toolbar' and gpodder.ui.desktop:
|
||||
self.toolbar.set_property('visible', new_value)
|
||||
elif name == 'videoplayer':
|
||||
self.config.video_played_dbus = False
|
||||
elif name == 'player':
|
||||
self.config.audio_played_dbus = False
|
||||
elif name == 'episode_list_descriptions':
|
||||
self.update_episode_list_model()
|
||||
elif name == 'episode_list_thumbnails':
|
||||
|
|
|
@ -30,6 +30,7 @@ _ = gpodder.gettext
|
|||
import atexit
|
||||
import datetime
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
|
@ -42,6 +43,19 @@ from gpodder import minidb
|
|||
import mygpoclient
|
||||
mygpoclient.user_agent += ' ' + gpodder.user_agent
|
||||
|
||||
MYGPOCLIENT_REQUIRED = '1.4'
|
||||
|
||||
if not hasattr(mygpoclient, 'require_version') or \
|
||||
not mygpoclient.require_version(MYGPOCLIENT_REQUIRED):
|
||||
print >>sys.stderr, """
|
||||
Please upgrade your mygpoclient library.
|
||||
See http://thpinfo.com/2010/mygpoclient/
|
||||
|
||||
Required version: %s
|
||||
Installed version: %s
|
||||
""" % (MYGPOCLIENT_REQUIRED, mygpoclient.__version__)
|
||||
sys.exit(1)
|
||||
|
||||
from mygpoclient import api
|
||||
|
||||
from mygpoclient import util as mygpoutil
|
||||
|
@ -112,13 +126,15 @@ class EpisodeAction(object):
|
|||
'started': int, 'position': int, 'total': int}
|
||||
|
||||
def __init__(self, podcast_url, episode_url, device_id, \
|
||||
action, timestamp, position):
|
||||
action, timestamp, started, position, total):
|
||||
self.podcast_url = podcast_url
|
||||
self.episode_url = episode_url
|
||||
self.device_id = device_id
|
||||
self.action = action
|
||||
self.timestamp = timestamp
|
||||
self.started = started
|
||||
self.position = position
|
||||
self.total = total
|
||||
|
||||
# New entity name for "received" actions
|
||||
class ReceivedEpisodeAction(EpisodeAction): pass
|
||||
|
@ -235,10 +251,15 @@ class MygPoClient(object):
|
|||
else:
|
||||
raise Exception('Webservice access not enabled')
|
||||
|
||||
def _convert_played_episode(self, episode, start, end, total):
|
||||
return EpisodeAction(episode.channel.url, \
|
||||
episode.url, self.device_id, 'play', \
|
||||
int(time.time()), start, end, total)
|
||||
|
||||
def _convert_episode(self, episode, action):
|
||||
return EpisodeAction(episode.channel.url, \
|
||||
episode.url, self.device_id, action, \
|
||||
int(time.time()), None)
|
||||
int(time.time()), None, None, None)
|
||||
|
||||
def on_delete(self, episodes):
|
||||
log('Storing %d episode delete actions', len(episodes), sender=self)
|
||||
|
@ -248,6 +269,10 @@ class MygPoClient(object):
|
|||
log('Storing %d episode download actions', len(episodes), sender=self)
|
||||
self._store.save(self._convert_episode(e, 'download') for e in episodes)
|
||||
|
||||
def on_playback_full(self, episode, start, end, total):
|
||||
log('Storing full episode playback action', sender=self)
|
||||
self._store.save(self._convert_played_episode(episode, start, end, total))
|
||||
|
||||
def on_playback(self, episodes):
|
||||
log('Storing %d episode playback actions', len(episodes), sender=self)
|
||||
self._store.save(self._convert_episode(e, 'play') for e in episodes)
|
||||
|
@ -372,14 +397,15 @@ class MygPoClient(object):
|
|||
return api.EpisodeAction(action.podcast_url, \
|
||||
action.episode_url, action.action, \
|
||||
action.device_id, since, \
|
||||
action.position)
|
||||
action.started, action.position, action.total)
|
||||
|
||||
def convert_from_api(action):
|
||||
dt = mygpoutil.iso8601_to_datetime(action.timestamp)
|
||||
since = int(dt.strftime('%s'))
|
||||
return ReceivedEpisodeAction(action.podcast, \
|
||||
action.episode, action.device, \
|
||||
action.action, since, action.position)
|
||||
action.action, since, \
|
||||
action.started, action.position, action.total)
|
||||
|
||||
try:
|
||||
save_since = True
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# gPodder - A media aggregator and podcast client
|
||||
# Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
|
||||
#
|
||||
# gPodder 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; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# gPodder 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
#
|
||||
# gpodder.player - Podcatcher implementation of the Media Player D-Bus API
|
||||
# Thomas Perl <thp@gpodder.org>; 2010-04-25
|
||||
#
|
||||
# Specification: http://gpodder.org/wiki/Media_Player_D-Bus_API
|
||||
#
|
||||
|
||||
|
||||
try:
|
||||
import dbus
|
||||
except ImportError:
|
||||
# Import Mock D-Bus interfaces when D-Bus bindings are not installed
|
||||
from gpodder.gui import dbus
|
||||
|
||||
|
||||
class MediaPlayerDBusReceiver(object):
|
||||
INTERFACE = 'org.gpodder.player'
|
||||
SIGNAL_STARTED = 'PlaybackStarted'
|
||||
SIGNAL_STOPPED = 'PlaybackStopped'
|
||||
|
||||
def __init__(self, on_play_event):
|
||||
self.on_play_event = on_play_event
|
||||
|
||||
self.bus = dbus.SessionBus()
|
||||
self.bus.add_signal_receiver(self.on_playback_started, \
|
||||
self.SIGNAL_STARTED, \
|
||||
self.INTERFACE, \
|
||||
None, \
|
||||
None)
|
||||
self.bus.add_signal_receiver(self.on_playback_stopped, \
|
||||
self.SIGNAL_STOPPED, \
|
||||
self.INTERFACE, \
|
||||
None, \
|
||||
None)
|
||||
|
||||
def on_playback_started(self, position, file_uri):
|
||||
pass
|
||||
|
||||
def on_playback_stopped(self, start, end, total, file_uri):
|
||||
if file_uri.startswith('/'):
|
||||
file_uri = 'file://' + file_uri
|
||||
self.on_play_event(start, end, total, file_uri)
|
||||
|
Loading…
Reference in New Issue