07b89b46fbe94fdd38c710b488d6672c1d08d856
[jamaendo] / jamaui / player.py
1 # Implements playback controls
2 # Gstreamer stuff mostly snibbed from Panucci
3 #
4 # This file is part of Panucci.
5 # Copyright (c) 2008-2009 The Panucci Audiobook and Podcast Player Project
6 #
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.
11 #
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.
16 #
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/>.
19 #
20
21 import logging
22 import pygst
23 pygst.require('0.10')
24 import gst
25 import util
26
27 log = logging.getLogger(__name__)
28
29
30 class GStreamer(object):
31     """Wraps GStreamer"""
32     STATES = { gst.STATE_NULL    : 'stopped',
33                gst.STATE_PAUSED  : 'paused',
34                gst.STATE_PLAYING : 'playing' }
35
36     def __init__(self):
37         self.time_format = gst.Format(gst.FORMAT_TIME)
38         self.player = None
39         self.filesrc = None
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()
45
46     def setup(self, filetype, uri):
47         if None in (filetype, uri):
48             self.player = None
49             return False
50
51         # On maemo use software decoding to workaround some bugs their gst:
52         # 1. Weird volume bugs in playbin when playing ogg or wma files
53         # 2. When seeking the DSPs sometimes lie about the real position info
54         if util.platform == 'maemo':
55             if not self._maemo_setup_hardware_player(filetype):
56                 self._maemo_setup_software_player()
57                 log.debug( 'Using software decoding (maemo)' )
58             else:
59                 log.debug( 'Using hardware decoding (maemo)' )
60         else:
61             # This is for *ahem* "normal" versions of gstreamer
62             self._setup_playbin_player()
63             log.debug( 'Using playbin (non-maemo)' )
64
65         self._set_uri_to_be_played(uri)
66
67         bus = self._player.get_bus()
68         bus.add_signal_watch()
69         bus.connect('message', self._on_message)
70         return True
71
72     def get_state(self):
73         if self.player:
74             state = self._player.get_state()[1]
75             return self.STATES.get(state, 'none')
76         return 'none'
77
78     def playing(self):
79         return self.get_state() == 'playing'
80
81     def play(self):
82         if self.player:
83             self.player.set_state(gst.STATE_PLAYING)
84
85     def pause(self):
86         if self.player:
87             self.player.set_state(gst.STATE_PAUSED)
88
89     def play_pause_toggle(self):
90         self.pause() if self.playing() else self.play()
91
92     def stop(self):
93         if self.player:
94             self.player.set_state(gst.STATE_NULL)
95             self.player = None
96
97     def _maemo_setup_hardware_player( self, filetype ):
98         """ Setup a hardware player for mp3 or aac audio using
99         dspaacsink or dspmp3sink """
100
101         if filetype in [ 'mp3', 'aac', 'mp4', 'm4a' ]:
102             self.player = gst.element_factory_make('playbin', 'player')
103             self.filesrc = self.player
104             self.filesrc_property = 'uri'
105             self.volume_control = self.player
106             self.volume_multiplier = 10.
107             self.volume_property = 'volume'
108             return True
109         else:
110             return False
111
112     def _maemo_setup_software_player( self ):
113         """
114         Setup a software decoding player for maemo, this is the only choice
115         for decoding wma and ogg or if audio is to be piped to a bluetooth
116         headset (this is because the audio must first be decoded only to be
117         re-encoded using sbcenc.
118         """
119
120         self.player = gst.Pipeline('player')
121         src = gst.element_factory_make('gnomevfssrc', 'src')
122         decoder = gst.element_factory_make('decodebin', 'decoder')
123         convert = gst.element_factory_make('audioconvert', 'convert')
124         resample = gst.element_factory_make('audioresample', 'resample')
125         sink = gst.element_factory_make('dsppcmsink', 'sink')
126
127         self.filesrc = src # pointer to the main source element
128         self.filesrc_property = 'location'
129         self.volume_control = sink
130         self.volume_multiplier = 1
131         self.volume_property = 'fvolume'
132
133         # Add the various elements to the player pipeline
134         self.player.add( src, decoder, convert, resample, sink )
135
136         # Link what can be linked now, the decoder->convert happens later
137         gst.element_link_many( src, decoder )
138         gst.element_link_many( convert, resample, sink )
139
140         # We can't link the two halves of the pipeline until it comes
141         # time to start playing, this singal lets us know when it's time.
142         # This is because the output from decoder can't be determined until
143         # decoder knows what it's decoding.
144         decoder.connect('pad-added',
145                         self._on_decoder_pad_added,
146                         convert.get_pad('sink') )
147
148     def _setup_playbin_player( self ):
149         """ This is for situations where we have a normal (read: non-maemo)
150         version of gstreamer like on a regular linux distro. """
151         self.player = gst.element_factory_make('playbin2', 'player')
152         self.filesrc = self.player
153         self.filesrc_property = 'uri'
154         self.volume_control = self.player
155         self.volume_multiplier = 1.
156         self.volume_property = 'volume'
157
158     def _on_decoder_pad_added(self, decoder, src_pad, sink_pad):
159         # link the decoder's new "src_pad" to "sink_pad"
160         src_pad.link( sink_pad )
161
162     def _get_volume_level(self):
163         if self.volume_control is not None:
164             vol = self.volume_control.get_property( self.volume_property )
165             return  vol / float(self.volume_multiplier)
166
167     def _set_volume_level(self, value):
168         assert  0 <= value <= 1
169
170         if self.volume_control is not None:
171             vol = value * self.volume_multiplier
172             self.volume_control.set_property( self.volume_property, vol )
173
174     def _set_uri_to_be_played(self, uri):
175         # Sets the right property depending on the platform of self.filesrc
176         if self.player is not None:
177             self.filesrc.set_property(self.filesrc_property, uri)
178
179     def _on_message(self, bus, message):
180         t = message.type
181
182         if t == gst.MESSAGE_EOS:
183             self.eos_callback()
184
185         elif t == gst.MESSAGE_ERROR:
186             err, debug = message.parse_error()
187             log.critical( 'Error: %s %s', err, debug )
188             self.stop()
189
190     def set_eos_callback(self, cb):
191         self.eos_callback = cb
192
193 class Playlist(object):
194     def __init__(self):
195         self.items = []
196
197     def add(self, item):
198         self.items.append(item)
199
200 class Player(Playlist):
201     def __init__(self):
202         self.gstreamer = GStreamer()
203         self.gstreamer.set_eos_callback(self._on_eos)
204
205     def play(self, item=None):
206         pass
207
208     def pause(self):
209         pass
210
211     def stop(self):
212         pass
213
214     def next(self):
215         pass
216
217     def prev(self):
218         pass
219
220     def _on_eos(self):
221         pass