X-Git-Url: http://git.maemo.org/git/?p=jamaendo;a=blobdiff_plain;f=jamaui%2Fplayer.py;h=24ec4f2e125a16d80916f02ac33615e2d73e7462;hp=0b27777a00bb510ce960b45255cfd0a059551156;hb=19020e8dae378875c8bfcd7e573ca5e91b1dfa10;hpb=ba8351e78b0507818a08587c4d9d7e32fb59bd62 diff --git a/jamaui/player.py b/jamaui/player.py index 0b27777..24ec4f2 100644 --- a/jamaui/player.py +++ b/jamaui/player.py @@ -23,52 +23,83 @@ import pygst pygst.require('0.10') import gst import util +import dbus + +import jamaendo +from settings import settings +from postoffice import postoffice log = logging.getLogger(__name__) +class _Player(object): + """Defines the internal player interface""" + def __init__(self): + pass + def play_url(self, filetype, uri): + raise NotImplemented + def playing(self): + raise NotImplemented + def play_pause_toggle(self): + self.pause() if self.playing() else self.play() + def play(self): + raise NotImplemented + def pause(self): + raise NotImplemented + def stop(self): + raise NotImplemented + def set_eos_callback(self, cb): + raise NotImplemented -class GStreamer(object): +class GStreamer(_Player): """Wraps GStreamer""" STATES = { gst.STATE_NULL : 'stopped', gst.STATE_PAUSED : 'paused', gst.STATE_PLAYING : 'playing' } def __init__(self): + _Player.__init__(self) self.time_format = gst.Format(gst.FORMAT_TIME) self.player = None self.filesrc = None self.filesrc_property = None self.volume_control = None - self.volume_multiplier = 1 + self.volume_multiplier = 1. self.volume_property = None self.eos_callback = lambda: self.stop() + postoffice.connect('settings-changed', self, self.on_settings_changed) + + def on_settings_changed(self, key, value): + if key == 'volume': + self._set_volume_level(value) + #postoffice.disconnect(self) - def setup(self, filetype, uri): + + def play_url(self, filetype, uri): if None in (filetype, uri): self.player = None return False - log.debug("Setting up for %s : %s", filetype, uri) - - # On maemo use software decoding to workaround some bugs their gst: - # 1. Weird volume bugs in playbin when playing ogg or wma files - # 2. When seeking the DSPs sometimes lie about the real position info - if util.platform == 'maemo': - if not self._maemo_setup_hardware_player(filetype): - self._maemo_setup_software_player() - log.debug( 'Using software decoding (maemo)' ) + _first = False + if self.player is None: + _first = True + if False: + self._maemo_setup_playbin2_player(uri) + log.debug('Using playbin2 (maemo)') + elif util.platform == 'maemo': + self._maemo_setup_playbin_player() + log.debug('Using playbin (maemo)') else: - log.debug( 'Using hardware decoding (maemo)' ) - else: - # This is for *ahem* "normal" versions of gstreamer - self._setup_playbin_player() - log.debug( 'Using playbin (non-maemo)' ) + self._setup_playbin_player() + log.debug( 'Using playbin (non-maemo)' ) + + bus = self.player.get_bus() + bus.add_signal_watch() + bus.connect('message', self._on_message) + self._set_volume_level(settings.volume) self._set_uri_to_be_played(uri) - bus = self.player.get_bus() - bus.add_signal_watch() - bus.connect('message', self._on_message) + self.play() return True def get_state(self): @@ -77,6 +108,15 @@ class GStreamer(object): return self.STATES.get(state, 'none') return 'none' + def get_position_duration(self): + try: + pos_int = self.player.query_position(self.time_format, None)[0] + dur_int = self.player.query_duration(self.time_format, None)[0] + except Exception, e: + log.exception('Error getting position') + pos_int = dur_int = 0 + return pos_int, dur_int + def playing(self): return self.get_state() == 'playing' @@ -89,64 +129,28 @@ class GStreamer(object): if self.player: self.player.set_state(gst.STATE_PAUSED) - def play_pause_toggle(self): - self.pause() if self.playing() else self.play() - - def stop(self): + def stop(self, reset = True): if self.player: self.player.set_state(gst.STATE_NULL) - self.player = None + if reset: + self.player = None - def _maemo_setup_hardware_player( self, filetype ): - """ Setup a hardware player for mp3 or aac audio using - dspaacsink or dspmp3sink """ - - if filetype in [ 'mp3', 'aac', 'mp4', 'm4a' ]: - self.player = gst.element_factory_make('playbin', 'player') - self.filesrc = self.player - self.filesrc_property = 'uri' - self.volume_control = self.player - self.volume_multiplier = 10. - self.volume_property = 'volume' - return True - else: - return False - - def _maemo_setup_software_player( self ): - """ - Setup a software decoding player for maemo, this is the only choice - for decoding wma and ogg or if audio is to be piped to a bluetooth - headset (this is because the audio must first be decoded only to be - re-encoded using sbcenc. - """ + def _maemo_setup_playbin2_player(self, url): + self.player = gst.parse_launch("playbin2 uri=%s" % (url,)) + self.filesrc = self.player + self.filesrc_property = 'uri' + self.volume_control = self.player + self.volume_multiplier = 1. + self.volume_property = 'volume' - self.player = gst.Pipeline('player') - src = gst.element_factory_make('gnomevfssrc', 'src') - decoder = gst.element_factory_make('decodebin', 'decoder') - convert = gst.element_factory_make('audioconvert', 'convert') - resample = gst.element_factory_make('audioresample', 'resample') - sink = gst.element_factory_make('dsppcmsink', 'sink') - - self.filesrc = src # pointer to the main source element - self.filesrc_property = 'location' - self.volume_control = sink - self.volume_multiplier = 1 - self.volume_property = 'fvolume' - - # Add the various elements to the player pipeline - self.player.add( src, decoder, convert, resample, sink ) - - # Link what can be linked now, the decoder->convert happens later - gst.element_link_many( src, decoder ) - gst.element_link_many( convert, resample, sink ) - - # We can't link the two halves of the pipeline until it comes - # time to start playing, this singal lets us know when it's time. - # This is because the output from decoder can't be determined until - # decoder knows what it's decoding. - decoder.connect('pad-added', - self._on_decoder_pad_added, - convert.get_pad('sink') ) + def _maemo_setup_playbin_player( self): + self.player = gst.element_factory_make('playbin2', 'player') + self.filesrc = self.player + self.filesrc_property = 'uri' + self.volume_control = self.player + self.volume_multiplier = 1. + self.volume_property = 'volume' + return True def _setup_playbin_player( self ): """ This is for situations where we have a normal (read: non-maemo) @@ -171,7 +175,8 @@ class GStreamer(object): assert 0 <= value <= 1 if self.volume_control is not None: - vol = value * self.volume_multiplier + vol = value * float(self.volume_multiplier) + log.debug("Setting volume to %s", vol) self.volume_control.set_property( self.volume_property, vol ) def _set_uri_to_be_played(self, uri): @@ -183,8 +188,12 @@ class GStreamer(object): t = message.type if t == gst.MESSAGE_EOS: + log.info("End of stream") self.eos_callback() - + elif t == gst.MESSAGE_STATE_CHANGED: + if (message.src == self.player and + message.structure['new-state'] == gst.STATE_PLAYING): + log.info("State changed to playing") elif t == gst.MESSAGE_ERROR: err, debug = message.parse_error() log.critical( 'Error: %s %s', err, debug ) @@ -193,55 +202,191 @@ class GStreamer(object): def set_eos_callback(self, cb): self.eos_callback = cb +if util.platform == 'maemo': + class OssoPlayer(_Player): + """ + A player which uses osso-media-player for playback (Maemo-specific) + """ + + SERVICE_NAME = "com.nokia.osso_media_server" + OBJECT_PATH = "/com/nokia/osso_media_server" + AUDIO_INTERFACE_NAME = "com.nokia.osso_media_server.music" + + def __init__(self): + self._on_eos = lambda: self.stop() + self._state = 'none' + self._audio = self._init_dbus() + self._init_signals() + + def play_url(self, filetype, uri): + self._audio.play_media(uri) + + def playing(self): + return self._state == 'playing' + + def play_pause_toggle(self): + self.pause() if self.playing() else self.play() + + def play(self): + self._audio.play() + + def pause(self): + if self.playing(): + self._audio.pause() + + def stop(self): + self._audio.stop() + + def set_eos_callback(self, cb): + self._on_eos = cb + + + def _init_dbus(self): + session_bus = dbus.SessionBus() + oms_object = session_bus.get_object(self.SERVICE_NAME, + self.OBJECT_PATH, + introspect = False, + follow_name_owner_changes = True) + return dbus.Interface(oms_object, self.AUDIO_INTERFACE_NAME) + + def _init_signals(self): + error_signals = { + "no_media_selected": "No media selected", + "file_not_found": "File not found", + "type_not_found": "Type not found", + "unsupported_type": "Unsupported type", + "gstreamer": "GStreamer Error", + "dsp": "DSP Error", + "device_unavailable": "Device Unavailable", + "corrupted_file": "Corrupted File", + "out_of_memory": "Out of Memory", + "audio_codec_not_supported": "Audio codec not supported" + } + + # Connect status signals + self._audio.connect_to_signal( "state_changed", + self._on_state_changed ) + self._audio.connect_to_signal( "end_of_stream", + lambda x: self._call_eos() ) + + # Connect error signals + for error, msg in error_signals.iteritems(): + self._audio.connect_to_signal(error, lambda *x: self._error(msg)) + + def _error(self, msg): + log.error(msg) + + def _call_eos(self): + self._on_eos() + + def _on_state_changed(self, state): + states = ("playing", "paused", "stopped") + self.__state = state if state in states else 'none' + +# PlayerBackend = OssoPlayer +#else: +PlayerBackend = GStreamer + class Playlist(object): def __init__(self, items = []): + if items is None: + items = [] + for item in items: + assert(isinstance(item, jamaendo.Track)) self.items = items - self.current = -1 + self._current = -1 def add(self, item): - self.items.append(item) + if isinstance(item, list): + for i in item: + assert(isinstance(i, jamaendo.Track)) + self.items.extend(item) + else: + self.items.append(item) def next(self): if self.has_next(): - self.current = self.current + 1 - return self.items[self.current] + self._current = self._current + 1 + return self.items[self._current] + return None + + def prev(self): + if self.has_prev(): + self._current = self._current - 1 + return self.items[self._current] return None def has_next(self): - return self.current < (len(self.items)-1) + return self._current < (len(self.items)-1) + + def has_prev(self): + return self._current > 0 + + def current(self): + if self._current >= 0: + return self.items[self._current] + return None + + def current_index(self): + return self._current + + def size(self): + return len(self.items) + + def __repr__(self): + return "Playlist(%s)"%(", ".join([str(item.ID) for item in self.items])) -class Player(Playlist): +class Player(object): def __init__(self): - self.gstreamer = GStreamer() - self.gstreamer.set_eos_callback(self._on_eos) - self.playlist = None + self.backend = PlayerBackend() + self.backend.set_eos_callback(self._on_eos) + self.playlist = Playlist() + + def get_position_duration(self): + return self.backend.get_position_duration() def play(self, playlist = None): if playlist: self.playlist = playlist - if self.playlist is not None: - if self.playlist.has_next(): - self.gstreamer.setup('mp3', self.playlist.next()) - self.gstreamer.play() + elif self.playlist is None: + self.playlist = Playlist() + if self.playlist.size(): + if self.playlist.current(): + entry = self.playlist.current() + self.backend.play_url('mp3', entry.mp3_url()) + log.debug("playing %s", entry) + elif self.playlist.has_next(): + entry = self.playlist.next() + self.backend.play_url('mp3', entry.mp3_url()) + log.debug("playing %s", entry) def pause(self): - self.gstreamer.pause() + self.backend.pause() def stop(self): - self.gstreamer.stop() + self.backend.stop() def playing(self): - return self.gstreamer.playing() + return self.backend.playing() def next(self): if self.playlist.has_next(): - self.gstreamer.setup('mp3', self.playlist.next()) - self.gstreamer.play() + self.backend.stop(reset=False) + entry = self.playlist.next() + self.backend.play_url('mp3', entry.mp3_url()) + log.debug("playing %s", entry) else: self.stop() def prev(self): - pass + if self.playlist.has_prev(): + self.backend.stop(reset=False) + entry = self.playlist.prev() + self.backend.play_url('mp3', entry.mp3_url()) + log.debug("playing %s", entry) def _on_eos(self): + log.debug("EOS!") self.next() + +the_player = Player() # the player instance