X-Git-Url: http://git.maemo.org/git/?p=jamaendo;a=blobdiff_plain;f=jamaui%2Fplayer.py;h=31211eba0a47450ab50b00a291a6bcea72d2b524;hp=13af7a6b7bbfe4ac3f08aa1c1c76064937bdf7c5;hb=e671a4f6b6ab6f73f4052222c259570b8d31567b;hpb=3eba0a22b3bffc6f661fa0334ad90ea1a844748c diff --git a/jamaui/player.py b/jamaui/player.py index 13af7a6..31211eb 100644 --- a/jamaui/player.py +++ b/jamaui/player.py @@ -24,9 +24,11 @@ pygst.require('0.10') import gst import util import dbus -import dbus.service import jamaendo +from settings import settings +from postoffice import postoffice +from fetcher import Fetcher log = logging.getLogger(__name__) @@ -62,40 +64,41 @@ class GStreamer(_Player): 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 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 True: - self._maemo_setup_playbin_player(uri) - elif 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._set_uri_to_be_played(uri) + 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) + bus = self.player.get_bus() + bus.add_signal_watch() + bus.connect('message', self._on_message) + self._set_volume_level(settings.volume) - self._set_volume_level( 1 ) + self._set_uri_to_be_played(uri) self.play() return True @@ -106,24 +109,33 @@ class GStreamer(_Player): 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' def play(self): if self.player: - log.debug("playing") self.player.set_state(gst.STATE_PLAYING) def pause(self): if self.player: self.player.set_state(gst.STATE_PAUSED) - 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_playbin_player(self, url): + def _maemo_setup_playbin2_player(self, url): self.player = gst.parse_launch("playbin2 uri=%s" % (url,)) self.filesrc = self.player self.filesrc_property = 'uri' @@ -131,56 +143,14 @@ class GStreamer(_Player): self.volume_multiplier = 1. self.volume_property = 'volume' - 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. - """ - - 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) @@ -189,7 +159,7 @@ class GStreamer(_Player): self.filesrc = self.player self.filesrc_property = 'uri' self.volume_control = self.player - self.volume_multiplier = 1. + self.volume_multiplier = 10. self.volume_property = 'volume' def _on_decoder_pad_added(self, decoder, src_pad, sink_pad): @@ -205,29 +175,30 @@ class GStreamer(_Player): 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): # Sets the right property depending on the platform of self.filesrc if self.player is not None: self.filesrc.set_property(self.filesrc_property, uri) + log.info("%s", uri) def _on_message(self, bus, message): t = message.type if t == gst.MESSAGE_EOS: + log.debug("Gstreamer: End of stream") self.eos_callback() - log.info("End of stream") - elif t == gst.MESSAGE_STATE_CHANGED: - old, new, pending = message.parse_state_changed() - log.info("State changed: %s -> %s -> %s", old, new, pending) + #elif t == gst.MESSAGE_STATE_CHANGED: + # if (message.src == self.player and + # message.structure['new-state'] == gst.STATE_PLAYING): + # log.debug("gstreamer: state -> playing") elif t == gst.MESSAGE_ERROR: err, debug = message.parse_error() log.critical( 'Error: %s %s', err, debug ) self.stop() - else: - log.info("? %s", message.type) def set_eos_callback(self, cb): self.eos_callback = cb @@ -319,6 +290,9 @@ PlayerBackend = GStreamer class Playlist(object): def __init__(self, items = []): + self.radio_mode = False + self.radio_id = None + self.radio_name = None if items is None: items = [] for item in items: @@ -357,57 +331,116 @@ class Playlist(object): return self.items[self._current] return None + def jump_to(self, item_id): + for c, i in enumerate(self.items): + if i.ID == item_id: + self._current = c + def current_index(self): return self._current def size(self): - print type(self) return len(self.items) def __repr__(self): - return "Playlist(%s)"%(", ".join([str(item) for item in self.items])) + return "Playlist(%d of %s)"%(self._current, ", ".join([str(item.ID) for item in self.items])) -class Player(Playlist): +class Player(object): def __init__(self): self.backend = PlayerBackend() self.backend.set_eos_callback(self._on_eos) self.playlist = Playlist() + self.fetcher = None # for refilling the radio + + def get_position_duration(self): + return self.backend.get_position_duration() + + def _play_track(self, track, notify='play'): + self.backend.play_url('mp3', track.mp3_url()) + log.debug("playing %s", track) + postoffice.notify(notify, track) + + def _refill_radio(self): + log.debug("Refilling radio %s", self.playlist) + #self.playlist.add(jamaendo.get_radio_tracks(self.playlist.radio_id)) + self._start_radio_fetcher() + + def _start_radio_fetcher(self): + if self.fetcher: + self.fetcher.stop() + self.fetcher = None + self.fetcher = Fetcher(lambda: jamaendo.get_radio_tracks(self.playlist.radio_id), + self, + on_item = self._on_radio_result, + on_ok = self._on_radio_complete, + on_fail = self._on_radio_complete) + self.fetcher.has_no_results = True + self.fetcher.start() + + def _on_radio_result(self, wnd, item): + if wnd is self: + self.playlist.add(item) + if not self.playing(): + if self.fetcher.has_no_results: + self.fetcher.has_no_results = False + entry = self.playlist.next() + self._play_track(entry) + + def _on_radio_complete(self, wnd, error=None): + if wnd is self: + if error: + self.stop() + self.fetcher.stop() + self.fetcher = None def play(self, playlist = None): if playlist: self.playlist = playlist elif self.playlist is None: self.playlist = Playlist() - if self.playlist.size(): - if self.playlist.has_next(): - entry = self.playlist.next() - log.debug("playing %s", entry) - self.backend.play_url('mp3', entry.mp3_url()) - def pause(self): - self.backend.pause() - - def stop(self): - self.backend.stop() - - def playing(self): - return self.backend.playing() + if self.playlist.current(): + entry = self.playlist.current() + self._play_track(entry) + elif self.playlist.has_next(): + entry = self.playlist.next() + self._play_track(entry) + elif self.playlist.radio_mode: + self._refill_radio() + #self.play() def next(self): if self.playlist.has_next(): - self.stop() - self.play() + self.backend.stop(reset=False) + entry = self.playlist.next() + self._play_track(entry, notify='next') + elif self.playlist.radio_mode: + self._refill_radio() + #if self.playlist.has_next(): + # self.next() + #else: + # self.stop() else: self.stop() def prev(self): if self.playlist.has_prev(): + self.backend.stop(reset=False) entry = self.playlist.prev() - log.debug("playing %s", entry) - self.backend.play_url('mp3', entry.mp3_url()) + self._play_track(entry, 'prev') + + def pause(self): + self.backend.pause() + postoffice.notify('pause', self.playlist.current()) + + def stop(self): + self.backend.stop() + postoffice.notify('stop', self.playlist.current()) + + def playing(self): + return self.backend.playing() def _on_eos(self): - log.debug("EOS!") self.next() the_player = Player() # the player instance