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/>.
27 log = logging.getLogger(__name__)
30 class GStreamer(object):
32 STATES = { gst.STATE_NULL : 'stopped',
33 gst.STATE_PAUSED : 'paused',
34 gst.STATE_PLAYING : 'playing' }
37 self.time_format = gst.Format(gst.FORMAT_TIME)
40 self.filesrc_property = None
41 self.volume_control = None
42 self.volume_multiplier = 1
43 self.volume_property = None
44 self.eos_callback = lambda: self.stop()
46 def setup(self, filetype, uri):
47 if None in (filetype, uri):
51 log.debug("Setting up for %s : %s", filetype, uri)
53 # On maemo use software decoding to workaround some bugs their gst:
54 # 1. Weird volume bugs in playbin when playing ogg or wma files
55 # 2. When seeking the DSPs sometimes lie about the real position info
56 if util.platform == 'maemo':
57 if not self._maemo_setup_hardware_player(filetype):
58 self._maemo_setup_software_player()
59 log.debug( 'Using software decoding (maemo)' )
61 log.debug( 'Using hardware decoding (maemo)' )
63 # This is for *ahem* "normal" versions of gstreamer
64 self._setup_playbin_player()
65 log.debug( 'Using playbin (non-maemo)' )
67 self._set_uri_to_be_played(uri)
69 bus = self.player.get_bus()
70 bus.add_signal_watch()
71 bus.connect('message', self._on_message)
73 self._set_volume_level( 1 )
78 state = self.player.get_state()[1]
79 return self.STATES.get(state, 'none')
83 return self.get_state() == 'playing'
88 self.player.set_state(gst.STATE_PLAYING)
92 self.player.set_state(gst.STATE_PAUSED)
94 def play_pause_toggle(self):
95 self.pause() if self.playing() else self.play()
99 self.player.set_state(gst.STATE_NULL)
102 def _maemo_setup_hardware_player( self, filetype ):
103 """ Setup a hardware player for mp3 or aac audio using
104 dspaacsink or dspmp3sink """
106 if filetype in [ 'mp3', 'aac', 'mp4', 'm4a' ]:
107 self.player = gst.element_factory_make('playbin', 'player')
108 self.filesrc = self.player
109 self.filesrc_property = 'uri'
110 self.volume_control = self.player
111 self.volume_multiplier = 10.
112 self.volume_property = 'volume'
117 def _maemo_setup_software_player( self ):
119 Setup a software decoding player for maemo, this is the only choice
120 for decoding wma and ogg or if audio is to be piped to a bluetooth
121 headset (this is because the audio must first be decoded only to be
122 re-encoded using sbcenc.
125 self.player = gst.Pipeline('player')
126 src = gst.element_factory_make('gnomevfssrc', 'src')
127 decoder = gst.element_factory_make('decodebin', 'decoder')
128 convert = gst.element_factory_make('audioconvert', 'convert')
129 resample = gst.element_factory_make('audioresample', 'resample')
130 sink = gst.element_factory_make('dsppcmsink', 'sink')
132 self.filesrc = src # pointer to the main source element
133 self.filesrc_property = 'location'
134 self.volume_control = sink
135 self.volume_multiplier = 1
136 self.volume_property = 'fvolume'
138 # Add the various elements to the player pipeline
139 self.player.add( src, decoder, convert, resample, sink )
141 # Link what can be linked now, the decoder->convert happens later
142 gst.element_link_many( src, decoder )
143 gst.element_link_many( convert, resample, sink )
145 # We can't link the two halves of the pipeline until it comes
146 # time to start playing, this singal lets us know when it's time.
147 # This is because the output from decoder can't be determined until
148 # decoder knows what it's decoding.
149 decoder.connect('pad-added',
150 self._on_decoder_pad_added,
151 convert.get_pad('sink') )
153 def _setup_playbin_player( self ):
154 """ This is for situations where we have a normal (read: non-maemo)
155 version of gstreamer like on a regular linux distro. """
156 self.player = gst.element_factory_make('playbin2', 'player')
157 self.filesrc = self.player
158 self.filesrc_property = 'uri'
159 self.volume_control = self.player
160 self.volume_multiplier = 1.
161 self.volume_property = 'volume'
163 def _on_decoder_pad_added(self, decoder, src_pad, sink_pad):
164 # link the decoder's new "src_pad" to "sink_pad"
165 src_pad.link( sink_pad )
167 def _get_volume_level(self):
168 if self.volume_control is not None:
169 vol = self.volume_control.get_property( self.volume_property )
170 return vol / float(self.volume_multiplier)
172 def _set_volume_level(self, value):
173 assert 0 <= value <= 1
175 if self.volume_control is not None:
176 vol = value * self.volume_multiplier
177 self.volume_control.set_property( self.volume_property, vol )
179 def _set_uri_to_be_played(self, uri):
180 # Sets the right property depending on the platform of self.filesrc
181 if self.player is not None:
182 self.filesrc.set_property(self.filesrc_property, uri)
184 def _on_message(self, bus, message):
187 if t == gst.MESSAGE_EOS:
190 elif t == gst.MESSAGE_ERROR:
191 err, debug = message.parse_error()
192 log.critical( 'Error: %s %s', err, debug )
195 def set_eos_callback(self, cb):
196 self.eos_callback = cb
198 class Playlist(object):
200 def __init__(self, data):
201 if isinstance(data, dict):
203 self.name = data['name']
204 self.numalbum = int(data['numalbum'])
205 self.url = data['mp3']
207 elif isinstance(data, basestring): # assume URI
214 return "{%s}" % (", ".join([str(self.name), str(self.numalbum), str(self.url)]))
216 def __init__(self, items = []):
217 self.items = [Playlist.Entry(item) for item in items]
221 self.items.append(Playlist.Entry(item))
225 self.current = self.current + 1
226 return self.items[self.current]
230 return self.current < (len(self.items)-1)
232 class Player(Playlist):
234 self.gstreamer = GStreamer()
235 self.gstreamer.set_eos_callback(self._on_eos)
238 def play(self, playlist = None):
240 self.playlist = playlist
241 if self.playlist is not None:
242 if self.playlist.has_next():
243 entry = self.playlist.next()
244 log.debug("playing %s", entry)
245 self.gstreamer.setup(entry.type, entry.url)
246 self.gstreamer.play()
249 self.gstreamer.pause()
252 self.gstreamer.stop()
255 return self.gstreamer.playing()
258 if self.playlist.has_next():