2 # -*- coding: utf-8 -*-
4 # gPodder - A media aggregator and podcast client
5 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
7 # gPodder is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # gPodder is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 # Maemo 5 Media Player / MAFW Playback Monitor
23 # Send playback status information to gPodder using D-Bus
24 # Thomas Perl <thp@gpodder.org>; 2010-08-16 / 2010-08-17
27 # The code below is based on experimentation with MAFW and real files,
28 # so it might not work in the general case. It worked fine for me with
29 # local and streaming files (audio/video), though. Blame missing docs!
38 class gPodderPlayer(dbus
.service
.Object
):
39 # Empty class with method definitions to send D-Bus signals
41 def __init__(self
, path
, name
):
42 dbus
.service
.Object
.__init
__(self
, object_path
=path
, bus_name
=name
)
44 # Signals for gPodder's media player integration
45 @dbus.service
.signal(dbus_interface
='org.gpodder.player', signature
='us')
46 def PlaybackStarted(self
, position
, file_uri
):
49 @dbus.service
.signal(dbus_interface
='org.gpodder.player', signature
='uuus')
50 def PlaybackStopped(self
, start_position
, end_position
, total_time
, \
54 class MafwPlaybackMonitor(object):
55 MAFW_RENDERER_OBJECT
= 'com.nokia.mafw.renderer.Mafw-Gst-Renderer-Plugin.gstrenderer'
56 MAFW_RENDERER_PATH
= '/com/nokia/mafw/renderer/gstrenderer'
57 MAFW_RENDERER_INTERFACE
= 'com.nokia.mafw.renderer'
58 MAFW_RENDERER_SIGNAL_MEDIA
= 'media_changed'
59 MAFW_RENDERER_SIGNAL_STATE
= 'state_changed'
61 MAFW_SENDER_PATH
= '/org/gpodder/maemo/mafw'
62 MAFW_SENDER_NAME
= 'org.gpodder.maemo.mafw'
64 class MafwPlayState(object):
70 def __init__(self
, bus
):
73 self
._is
_playing
= False
74 self
._start
_time
= time
.time()
75 self
._start
_position
= 0
77 self
._player
= gPodderPlayer(self
.MAFW_SENDER_PATH
, \
78 dbus
.service
.BusName(self
.MAFW_SENDER_NAME
, self
.bus
))
80 state
, object_id
= self
.get_status()
82 self
.on_media_changed(0, object_id
)
83 self
.on_state_changed(state
)
85 self
.bus
.add_signal_receiver(self
.on_media_changed
, \
86 self
.MAFW_RENDERER_SIGNAL_MEDIA
, \
87 self
.MAFW_RENDERER_INTERFACE
, \
89 self
.MAFW_RENDERER_PATH
)
91 self
.bus
.add_signal_receiver(self
.on_state_changed
, \
92 self
.MAFW_RENDERER_SIGNAL_STATE
, \
93 self
.MAFW_RENDERER_INTERFACE
, \
95 self
.MAFW_RENDERER_PATH
)
97 # Capture requests to the renderer where the position is to
98 # be set to something else (or when it is to be stopped),
99 # because we don't get normal signals in these cases
100 bus
.add_match_string("type='method_call',destination='com.nokia.mafw.renderer.Mafw-Gst-Renderer-Plugin.gstrenderer',path='/com/nokia/mafw/renderer/gstrenderer',interface='com.nokia.mafw.renderer'")
101 bus
.add_message_filter(self
._message
_filter
)
103 def _message_filter(self
, bus
, message
):
104 if message
.get_path() == self
.MAFW_RENDERER_PATH
and \
105 message
.get_interface() == self
.MAFW_RENDERER_INTERFACE
and \
106 message
.get_destination() == self
.MAFW_RENDERER_OBJECT
and \
107 message
.get_type() == 1: # message type 1 == method call?
108 if message
.get_member() in ('set_position', 'stop'):
110 # Fake stop-of-old / start-of-new
111 self
.on_state_changed(-1)
112 self
.on_state_changed(self
.MafwPlayState
.Playing
)
114 def object_id_to_filename(self
, object_id
):
115 # Naive, but works for now...
116 if object_id
.startswith('localtagfs::'):
117 return 'file://'+urllib
.quote(urllib
.unquote(object_id
[object_id
.index('%2F'):]))
118 elif object_id
.startswith('urisource::'):
119 return object_id
[len('urisource::'):]
121 # This is pretty bad, actually (can happen with other
122 # sources, but should not happen for gPodder episodes)
127 o
= self
.bus
.get_object(self
.MAFW_RENDERER_OBJECT
, \
128 self
.MAFW_RENDERER_PATH
)
129 return dbus
.Interface(o
, self
.MAFW_RENDERER_INTERFACE
)
131 def get_position(self
):
132 return self
.renderer
.get_position()
134 def set_position(self
, position
):
135 self
.renderer
.set_position(0, position
)
138 def get_status(self
):
139 """Returns playing status and updates filename"""
140 playlist
, index
, state
, object_id
= self
.renderer
.get_status()
141 return (state
, object_id
)
143 def on_media_changed(self
, position
, object_id
):
145 # Fake stop-of-old / start-of-new
146 self
.on_state_changed(-1) # (see below where we catch the "-1")
147 self
._filename
= self
.object_id_to_filename(object_id
)
148 self
.on_state_changed(self
.MafwPlayState
.Playing
)
150 self
._filename
= self
.object_id_to_filename(object_id
)
152 def on_state_changed(self
, state
):
153 if state
== self
.MafwPlayState
.Playing
:
154 self
._is
_playing
= True
156 self
._start
_position
= self
.get_position()
160 self
._start
_time
= time
.time()
161 self
._player
.PlaybackStarted(self
._start
_position
, self
._filename
)
165 # Lame: if state is -1 (a faked "stop" event), don't try to
166 # get the "current" position, but use the wall time method
167 # from below to calculate the stop time
170 position
= self
.get_position()
172 # Happens if the assertion fails or if the position cannot
173 # be determined for whatever reason. Use wall time and
174 # assume that the media file has advanced the same amount.
175 position
= self
._start
_position
+ (time
.time()-self
._start
_time
)
176 if self
._start
_position
!= position
:
177 self
._player
.PlaybackStopped(self
._start
_position
, position
, 0, self
._filename
)
178 self
._is
_playing
= False
181 if __name__
== "__main__":
182 from dbus
.mainloop
.glib
import DBusGMainLoop
183 DBusGMainLoop(set_as_default
=True)
184 bus
= dbus
.SessionBus()
185 monitor
= MafwPlaybackMonitor(bus
)