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/>.
31 log = logging.getLogger(__name__)
33 class _Player(object):
34 """Defines the internal player interface"""
37 def play_url(self, filetype, uri):
41 def play_pause_toggle(self):
42 self.pause() if self.playing() else self.play()
49 def set_eos_callback(self, cb):
52 class GStreamer(_Player):
54 STATES = { gst.STATE_NULL : 'stopped',
55 gst.STATE_PAUSED : 'paused',
56 gst.STATE_PLAYING : 'playing' }
59 _Player.__init__(self)
60 self.time_format = gst.Format(gst.FORMAT_TIME)
63 self.filesrc_property = None
64 self.volume_control = None
65 self.volume_multiplier = 1
66 self.volume_property = None
67 self.eos_callback = lambda: self.stop()
69 def play_url(self, filetype, uri):
70 if None in (filetype, uri):
74 log.debug("Setting up for %s : %s", filetype, uri)
76 # On maemo use software decoding to workaround some bugs their gst:
77 # 1. Weird volume bugs in playbin when playing ogg or wma files
78 # 2. When seeking the DSPs sometimes lie about the real position info
80 self._maemo_setup_playbin_player(uri)
81 elif util.platform == 'maemo':
82 if not self._maemo_setup_hardware_player(filetype):
83 self._maemo_setup_software_player()
84 log.debug( 'Using software decoding (maemo)' )
86 log.debug( 'Using hardware decoding (maemo)' )
88 # This is for *ahem* "normal" versions of gstreamer
89 self._setup_playbin_player()
90 log.debug( 'Using playbin (non-maemo)' )
92 self._set_uri_to_be_played(uri)
94 bus = self.player.get_bus()
95 bus.add_signal_watch()
96 bus.connect('message', self._on_message)
98 self._set_volume_level( 1 )
105 state = self.player.get_state()[1]
106 return self.STATES.get(state, 'none')
110 return self.get_state() == 'playing'
115 self.player.set_state(gst.STATE_PLAYING)
119 self.player.set_state(gst.STATE_PAUSED)
123 self.player.set_state(gst.STATE_NULL)
126 def _maemo_setup_playbin_player(self, url):
127 self.player = gst.parse_launch("playbin2 uri=%s" % (url,))
128 self.filesrc = self.player
129 self.filesrc_property = 'uri'
130 self.volume_control = self.player
131 self.volume_multiplier = 1.
132 self.volume_property = 'volume'
134 def _maemo_setup_hardware_player( self, filetype ):
135 """ Setup a hardware player for mp3 or aac audio using
136 dspaacsink or dspmp3sink """
138 if filetype in [ 'mp3', 'aac', 'mp4', 'm4a' ]:
139 self.player = gst.element_factory_make('playbin', 'player')
140 self.filesrc = self.player
141 self.filesrc_property = 'uri'
142 self.volume_control = self.player
143 self.volume_multiplier = 10.
144 self.volume_property = 'volume'
149 def _maemo_setup_software_player( self ):
151 Setup a software decoding player for maemo, this is the only choice
152 for decoding wma and ogg or if audio is to be piped to a bluetooth
153 headset (this is because the audio must first be decoded only to be
154 re-encoded using sbcenc.
157 self.player = gst.Pipeline('player')
158 src = gst.element_factory_make('gnomevfssrc', 'src')
159 decoder = gst.element_factory_make('decodebin', 'decoder')
160 convert = gst.element_factory_make('audioconvert', 'convert')
161 resample = gst.element_factory_make('audioresample', 'resample')
162 sink = gst.element_factory_make('dsppcmsink', 'sink')
164 self.filesrc = src # pointer to the main source element
165 self.filesrc_property = 'location'
166 self.volume_control = sink
167 self.volume_multiplier = 1
168 self.volume_property = 'fvolume'
170 # Add the various elements to the player pipeline
171 self.player.add( src, decoder, convert, resample, sink )
173 # Link what can be linked now, the decoder->convert happens later
174 gst.element_link_many( src, decoder )
175 gst.element_link_many( convert, resample, sink )
177 # We can't link the two halves of the pipeline until it comes
178 # time to start playing, this singal lets us know when it's time.
179 # This is because the output from decoder can't be determined until
180 # decoder knows what it's decoding.
181 decoder.connect('pad-added',
182 self._on_decoder_pad_added,
183 convert.get_pad('sink') )
185 def _setup_playbin_player( self ):
186 """ This is for situations where we have a normal (read: non-maemo)
187 version of gstreamer like on a regular linux distro. """
188 self.player = gst.element_factory_make('playbin2', 'player')
189 self.filesrc = self.player
190 self.filesrc_property = 'uri'
191 self.volume_control = self.player
192 self.volume_multiplier = 1.
193 self.volume_property = 'volume'
195 def _on_decoder_pad_added(self, decoder, src_pad, sink_pad):
196 # link the decoder's new "src_pad" to "sink_pad"
197 src_pad.link( sink_pad )
199 def _get_volume_level(self):
200 if self.volume_control is not None:
201 vol = self.volume_control.get_property( self.volume_property )
202 return vol / float(self.volume_multiplier)
204 def _set_volume_level(self, value):
205 assert 0 <= value <= 1
207 if self.volume_control is not None:
208 vol = value * self.volume_multiplier
209 self.volume_control.set_property( self.volume_property, vol )
211 def _set_uri_to_be_played(self, uri):
212 # Sets the right property depending on the platform of self.filesrc
213 if self.player is not None:
214 self.filesrc.set_property(self.filesrc_property, uri)
216 def _on_message(self, bus, message):
219 if t == gst.MESSAGE_EOS:
221 log.info("End of stream")
222 elif t == gst.MESSAGE_STATE_CHANGED:
223 old, new, pending = message.parse_state_changed()
224 log.info("State changed: %s -> %s -> %s", old, new, pending)
225 elif t == gst.MESSAGE_ERROR:
226 err, debug = message.parse_error()
227 log.critical( 'Error: %s %s', err, debug )
230 log.info("? %s", message.type)
232 def set_eos_callback(self, cb):
233 self.eos_callback = cb
235 if util.platform == 'maemo':
236 class OssoPlayer(_Player):
238 A player which uses osso-media-player for playback (Maemo-specific)
241 SERVICE_NAME = "com.nokia.osso_media_server"
242 OBJECT_PATH = "/com/nokia/osso_media_server"
243 AUDIO_INTERFACE_NAME = "com.nokia.osso_media_server.music"
246 self._on_eos = lambda: self.stop()
248 self._audio = self._init_dbus()
251 def play_url(self, filetype, uri):
252 self._audio.play_media(uri)
255 return self._state == 'playing'
257 def play_pause_toggle(self):
258 self.pause() if self.playing() else self.play()
270 def set_eos_callback(self, cb):
274 def _init_dbus(self):
275 session_bus = dbus.SessionBus()
276 oms_object = session_bus.get_object(self.SERVICE_NAME,
279 follow_name_owner_changes = True)
280 return dbus.Interface(oms_object, self.AUDIO_INTERFACE_NAME)
282 def _init_signals(self):
284 "no_media_selected": "No media selected",
285 "file_not_found": "File not found",
286 "type_not_found": "Type not found",
287 "unsupported_type": "Unsupported type",
288 "gstreamer": "GStreamer Error",
290 "device_unavailable": "Device Unavailable",
291 "corrupted_file": "Corrupted File",
292 "out_of_memory": "Out of Memory",
293 "audio_codec_not_supported": "Audio codec not supported"
296 # Connect status signals
297 self._audio.connect_to_signal( "state_changed",
298 self._on_state_changed )
299 self._audio.connect_to_signal( "end_of_stream",
300 lambda x: self._call_eos() )
302 # Connect error signals
303 for error, msg in error_signals.iteritems():
304 self._audio.connect_to_signal(error, lambda *x: self._error(msg))
306 def _error(self, msg):
312 def _on_state_changed(self, state):
313 states = ("playing", "paused", "stopped")
314 self.__state = state if state in states else 'none'
316 # PlayerBackend = OssoPlayer
318 PlayerBackend = GStreamer
320 class Playlist(object):
321 def __init__(self, items = []):
325 assert(isinstance(item, jamaendo.Track))
330 if isinstance(item, list):
332 assert(isinstance(i, jamaendo.Track))
333 self.items.extend(item)
335 self.items.append(item)
339 self._current = self._current + 1
340 return self.items[self._current]
345 self._current = self._current - 1
346 return self.items[self._current]
350 return self._current < (len(self.items)-1)
353 return self._current > 0
356 if self._current >= 0:
357 return self.items[self._current]
360 def current_index(self):
365 return len(self.items)
368 return "Playlist(%s)"%(", ".join([str(item) for item in self.items]))
370 class Player(Playlist):
372 self.backend = PlayerBackend()
373 self.backend.set_eos_callback(self._on_eos)
374 self.playlist = Playlist()
376 def play(self, playlist = None):
378 self.playlist = playlist
379 elif self.playlist is None:
380 self.playlist = Playlist()
381 if self.playlist.size():
382 if self.playlist.has_next():
383 entry = self.playlist.next()
384 log.debug("playing %s", entry)
385 self.backend.play_url('mp3', entry.mp3_url())
394 return self.backend.playing()
397 if self.playlist.has_next():
404 if self.playlist.has_prev():
405 entry = self.playlist.prev()
406 log.debug("playing %s", entry)
407 self.backend.play_url('mp3', entry.mp3_url())