1 # Implements playback controls
2 # Gstreamer stuff mostly snibbed from Panucci
4 # This file is part of Panucci.
5 # Copyright (c) 2008-2009 The Panucci Audiobook and Podcast Player Project
7 # Panucci 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 # Panucci 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 Panucci. If not, see <http://www.gnu.org/licenses/>.
29 from settings import settings
30 from postoffice import postoffice
32 log = logging.getLogger(__name__)
34 class _Player(object):
35 """Defines the internal player interface"""
38 def play_url(self, filetype, uri):
42 def play_pause_toggle(self):
43 self.pause() if self.playing() else self.play()
50 def set_eos_callback(self, cb):
53 class GStreamer(_Player):
55 STATES = { gst.STATE_NULL : 'stopped',
56 gst.STATE_PAUSED : 'paused',
57 gst.STATE_PLAYING : 'playing' }
60 _Player.__init__(self)
61 self.time_format = gst.Format(gst.FORMAT_TIME)
64 self.filesrc_property = None
65 self.volume_control = None
66 self.volume_multiplier = 1.
67 self.volume_property = None
68 self.eos_callback = lambda: self.stop()
69 postoffice.connect('settings-changed', self.on_settings_changed)
71 def on_settings_changed(self, key, value):
73 self._set_volume_level(value)
74 #postoffice.disconnect(self.on_settings_changed)
77 def play_url(self, filetype, uri):
78 if None in (filetype, uri):
83 if self.player is None:
86 self._maemo_setup_playbin2_player(uri)
87 log.debug('Using playbin2 (maemo)')
88 elif util.platform == 'maemo':
89 self._maemo_setup_playbin_player()
90 log.debug('Using playbin (maemo)')
92 self._setup_playbin_player()
93 log.debug( 'Using playbin (non-maemo)' )
95 bus = self.player.get_bus()
96 bus.add_signal_watch()
97 bus.connect('message', self._on_message)
98 self._set_volume_level(settings.volume)
100 self._set_uri_to_be_played(uri)
107 state = self.player.get_state()[1]
108 return self.STATES.get(state, 'none')
111 def _get_position_duration(self):
113 pos_int = self.player.query_position(self.time_format, None)[0]
114 dur_int = self.player.query_duration(self.time_format, None)[0]
116 log.exception('Error getting position')
117 pos_int = dur_int = 0
118 return pos_int, dur_int
121 return self.get_state() == 'playing'
126 self.player.set_state(gst.STATE_PLAYING)
130 self.player.set_state(gst.STATE_PAUSED)
134 self.player.set_state(gst.STATE_NULL)
137 def _maemo_setup_playbin2_player(self, url):
138 self.player = gst.parse_launch("playbin2 uri=%s" % (url,))
139 self.filesrc = self.player
140 self.filesrc_property = 'uri'
141 self.volume_control = self.player
142 self.volume_multiplier = 1.
143 self.volume_property = 'volume'
145 def _maemo_setup_playbin_player( self):
146 self.player = gst.element_factory_make('playbin2', 'player')
147 self.filesrc = self.player
148 self.filesrc_property = 'uri'
149 self.volume_control = self.player
150 self.volume_multiplier = 1.
151 self.volume_property = 'volume'
154 def _setup_playbin_player( self ):
155 """ This is for situations where we have a normal (read: non-maemo)
156 version of gstreamer like on a regular linux distro. """
157 self.player = gst.element_factory_make('playbin2', 'player')
158 self.filesrc = self.player
159 self.filesrc_property = 'uri'
160 self.volume_control = self.player
161 self.volume_multiplier = 1.
162 self.volume_property = 'volume'
164 def _on_decoder_pad_added(self, decoder, src_pad, sink_pad):
165 # link the decoder's new "src_pad" to "sink_pad"
166 src_pad.link( sink_pad )
168 def _get_volume_level(self):
169 if self.volume_control is not None:
170 vol = self.volume_control.get_property( self.volume_property )
171 return vol / float(self.volume_multiplier)
173 def _set_volume_level(self, value):
174 assert 0 <= value <= 1
176 if self.volume_control is not None:
177 vol = value * float(self.volume_multiplier)
178 log.debug("Setting volume to %s", vol)
179 self.volume_control.set_property( self.volume_property, vol )
181 def _set_uri_to_be_played(self, uri):
182 # Sets the right property depending on the platform of self.filesrc
183 if self.player is not None:
184 self.filesrc.set_property(self.filesrc_property, uri)
186 def _on_message(self, bus, message):
189 if t == gst.MESSAGE_EOS:
190 log.info("End of stream")
192 elif t == gst.MESSAGE_STATE_CHANGED:
193 if (message.src == self.player and
194 message.structure['new-state'] == gst.STATE_PLAYING):
195 log.info("State changed to playing")
196 elif t == gst.MESSAGE_ERROR:
197 err, debug = message.parse_error()
198 log.critical( 'Error: %s %s', err, debug )
201 def set_eos_callback(self, cb):
202 self.eos_callback = cb
204 if util.platform == 'maemo':
205 class OssoPlayer(_Player):
207 A player which uses osso-media-player for playback (Maemo-specific)
210 SERVICE_NAME = "com.nokia.osso_media_server"
211 OBJECT_PATH = "/com/nokia/osso_media_server"
212 AUDIO_INTERFACE_NAME = "com.nokia.osso_media_server.music"
215 self._on_eos = lambda: self.stop()
217 self._audio = self._init_dbus()
220 def play_url(self, filetype, uri):
221 self._audio.play_media(uri)
224 return self._state == 'playing'
226 def play_pause_toggle(self):
227 self.pause() if self.playing() else self.play()
239 def set_eos_callback(self, cb):
243 def _init_dbus(self):
244 session_bus = dbus.SessionBus()
245 oms_object = session_bus.get_object(self.SERVICE_NAME,
248 follow_name_owner_changes = True)
249 return dbus.Interface(oms_object, self.AUDIO_INTERFACE_NAME)
251 def _init_signals(self):
253 "no_media_selected": "No media selected",
254 "file_not_found": "File not found",
255 "type_not_found": "Type not found",
256 "unsupported_type": "Unsupported type",
257 "gstreamer": "GStreamer Error",
259 "device_unavailable": "Device Unavailable",
260 "corrupted_file": "Corrupted File",
261 "out_of_memory": "Out of Memory",
262 "audio_codec_not_supported": "Audio codec not supported"
265 # Connect status signals
266 self._audio.connect_to_signal( "state_changed",
267 self._on_state_changed )
268 self._audio.connect_to_signal( "end_of_stream",
269 lambda x: self._call_eos() )
271 # Connect error signals
272 for error, msg in error_signals.iteritems():
273 self._audio.connect_to_signal(error, lambda *x: self._error(msg))
275 def _error(self, msg):
281 def _on_state_changed(self, state):
282 states = ("playing", "paused", "stopped")
283 self.__state = state if state in states else 'none'
285 # PlayerBackend = OssoPlayer
287 PlayerBackend = GStreamer
289 class Playlist(object):
290 def __init__(self, items = []):
294 assert(isinstance(item, jamaendo.Track))
299 if isinstance(item, list):
301 assert(isinstance(i, jamaendo.Track))
302 self.items.extend(item)
304 self.items.append(item)
308 self._current = self._current + 1
309 return self.items[self._current]
314 self._current = self._current - 1
315 return self.items[self._current]
319 return self._current < (len(self.items)-1)
322 return self._current > 0
325 if self._current >= 0:
326 return self.items[self._current]
329 def current_index(self):
333 return len(self.items)
336 return "Playlist(%s)"%(", ".join([str(item.ID) for item in self.items]))
338 class Player(object):
340 self.backend = PlayerBackend()
341 self.backend.set_eos_callback(self._on_eos)
342 self.playlist = Playlist()
344 def play(self, playlist = None):
346 self.playlist = playlist
347 elif self.playlist is None:
348 self.playlist = Playlist()
349 if self.playlist.size():
350 if self.playlist.has_next():
351 entry = self.playlist.next()
352 log.debug("playing %s", entry)
353 self.backend.play_url('mp3', entry.mp3_url())
362 return self.backend.playing()
365 if self.playlist.has_next():
372 if self.playlist.has_prev():
373 entry = self.playlist.prev()
374 log.debug("playing %s", entry)
375 self.backend.play_url('mp3', entry.mp3_url())
381 the_player = Player() # the player instance