From 1f3d53701aafaf2685ef21295f7fb21fa89e13bf Mon Sep 17 00:00:00 2001 From: Jens Persson Date: Sat, 28 May 2011 14:58:46 +0200 Subject: [PATCH] reworked player backend --- src/panucci/backends/base.py | 17 ++- src/panucci/backends/gstreamer.py | 71 +++--------- src/panucci/dbusinterface.py | 59 +++++----- src/panucci/gtkui/gtkmain.py | 66 +++++------ src/panucci/player.py | 58 ++-------- src/panucci/playlist.py | 233 ++++++++++++++++++++++++++++---------- src/panucci/qtui/qtmain.py | 44 ++++--- 7 files changed, 288 insertions(+), 260 deletions(-) diff --git a/src/panucci/backends/base.py b/src/panucci/backends/base.py index a782959..e3df590 100644 --- a/src/panucci/backends/base.py +++ b/src/panucci/backends/base.py @@ -105,14 +105,13 @@ class BasePlayer(services.ObservableService): """ return self._play() - def stop(self, player=True): + def stop(self): """ Stops playback. - Params: player, boolean if delete player Returns: Nothing Signals: Must emit the "stopped" signal. """ - return self._stop(player) + return self._stop() def seek(self, position): """ Seek to an absolute position in the current file. @@ -125,7 +124,7 @@ class BasePlayer(services.ObservableService): ############################################# # Generic Functions - def do_seek(self, from_beginning=None, from_current=None, percent=None ): + def do_seek(self, from_beginning=None, from_current=None, percent=None): """ A very flexible function to seek in the current file Params: Requires ONE of the following keyword arguments @@ -180,6 +179,16 @@ class BasePlayer(services.ObservableService): """ Is the player paused? """ return self.get_state() == self.STATE_PAUSED + @property + def stopped(self): + """ Is the player stopped? """ + return self.get_state() == self.STATE_STOPPED + + @property + def null(self): + """ Is the player stopped? """ + return self.get_state() == self.STATE_NULL + def set_position_duration(self, pos, dur): """ used for setting pos and dur on startup""" self.__position, self.__duration = pos, dur diff --git a/src/panucci/backends/gstreamer.py b/src/panucci/backends/gstreamer.py index 5be224e..01279c1 100644 --- a/src/panucci/backends/gstreamer.py +++ b/src/panucci/backends/gstreamer.py @@ -22,32 +22,23 @@ import gobject import gst import logging -import panucci -from panucci import util from panucci.backends import base gobject.threads_init() class Player(base.BasePlayer): - """ A player that uses Gstreamer for playback """ + """A player that uses Gstreamer for playback""" def __init__(self): base.BasePlayer.__init__(self) self.__log = logging.getLogger('panucci.backends.GStreamerPlayer') - - # have we preformed the initial seek? - self.__initial_seek_completed = False - self._player = None - self._filesrc = None - self._filesrc_property = None - self._time_format = gst.Format(gst.FORMAT_TIME) - self._current_filetype = None + self._current_uri = 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] + pos_int = self._player.query_position(gst.FORMAT_TIME, None)[0] + dur_int = self._player.query_duration(gst.FORMAT_TIME, None)[0] except Exception, e: self.__log.exception('Error getting position...') pos_int = dur_int = 0 @@ -64,16 +55,10 @@ class Player(base.BasePlayer): gst.STATE_PLAYING : self.STATE_PLAYING }.get( state, self.STATE_NULL ) - def _load_media( self, uri ): - filetype = util.detect_filetype(uri) - - if filetype != self._current_filetype or self._player is None: - self.__setup_player() - - if self._player is not None: - self._filesrc.set_property( self._filesrc_property, uri ) - - self._current_filetype = filetype + def _load_media(self, uri): + self.__setup_player() + self._player.set_property("uri", uri) + self._current_uri = uri def _pause(self): pos, dur = self.get_position_duration() @@ -82,64 +67,40 @@ class Player(base.BasePlayer): return pos def _play(self): - have_player = self._player is not None - - # Don't think this is needed - #if have_player or self.__setup_player(): - if have_player: - self._initial_seek_completed = have_player + if self._current_uri and (self._player or not self._load_media(self._current_uri)): self._player.set_state(gst.STATE_PLAYING) return True else: - # should something happen here? perhaps self.stop()? return False - def _stop(self, player): + def _stop(self): self.notify('stopped', caller=self.stop) - if self._player is not None: + if self._player: self._player.set_state(gst.STATE_NULL) self.set_position_duration(0, 0) - if player: - self._player = None + self._player = None def _seek(self, position): self.seeking = True error = False - try: - self._player.seek_simple( - self._time_format, gst.SEEK_FLAG_FLUSH, position ) + self._player.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, position) except Exception, e: self.__log.exception( 'Error seeking' ) error = True - self.seeking = False return not error def __setup_player(self): - if self._setup_player(self._current_filetype): - bus = self._player.get_bus() - bus.add_signal_watch() - bus.connect('message', self.__on_message) - return True - - return False - - def _setup_player(self, filetype=None): self.__log.debug("Creating playbin-based gstreamer player") try: self._player = gst.element_factory_make('playbin2', 'player') except: self._player = gst.element_factory_make('playbin', 'player') - self._filesrc = self._player - self._filesrc_property = 'uri' - - return True - - def _on_decoder_pad_added(self, decoder, src_pad, sink_pad): - # link the decoder's new "src_pad" to "sink_pad" - src_pad.link( sink_pad ) + bus = self._player.get_bus() + bus.add_signal_watch() + bus.connect('message', self.__on_message) def __on_message(self, bus, message): t = message.type diff --git a/src/panucci/dbusinterface.py b/src/panucci/dbusinterface.py index 5af8d7d..ee79878 100644 --- a/src/panucci/dbusinterface.py +++ b/src/panucci/dbusinterface.py @@ -34,7 +34,7 @@ class panucciInterface(dbus.service.Object): self.__log = logging.getLogger('panucci.dbusinterface.panucciInterface') dbus.service.Object.__init__(self, object_path=path, bus_name=bus_name) - self.player = None + self.playlist = None self.gui = None self.headset_device = None @@ -45,9 +45,9 @@ class panucciInterface(dbus.service.Object): except Exception, e: self.__log.debug('Could not find headset object (on Maemo)') - def register_player(self, player): - self.__log.debug('Registered player.') - self.player = player + def register_playlist(self, playlist): + self.__log.debug('Registered playlist.') + self.playlist = playlist def register_gui(self, gui): self.__log.debug('Registered GUI.') @@ -64,63 +64,63 @@ class panucciInterface(dbus.service.Object): @dbus.service.method('org.panucci.panucciInterface') def play(self): self.__log.debug('play() called') - if self.player is not None: self.player.play() + if self.playlist: self.playlist.play() @dbus.service.method('org.panucci.panucciInterface') def pause(self): self.__log.debug('pause() called') - if self.player is not None: self.player.pause() + if self.playlist: self.playlist.pause() @dbus.service.method('org.panucci.panucciInterface') def stop(self): self.__log.debug('stop() called') - if self.player is not None: self.player.stop() + if self.playlist: self.playlist.stop() @dbus.service.method('org.panucci.panucciInterface') def playPause(self): self.__log.debug('playPause() called') - if self.player is not None: self.player.play_pause_toggle() + if self.playlist: self.playlist.play_pause_toggle() @dbus.service.method('org.panucci.panucciInterface') def playNext(self): self.__log.debug('playNext() called') - if self.player is not None: self.player.play_next() + if self.playlist: self.playlist.next() @dbus.service.method('org.panucci.panucciInterface') def playPrev(self): self.__log.debug('playPrev() called') - if self.player is not None: self.player.play_prev() + if self.playlist: self.playlist.prev() @dbus.service.method('org.panucci.panucciInterface') def backShort(self): self.__log.debug('backShort() called') seek_amount = self.gui.config.getint("options", "seek_short") - if self.player is not None: self.player.do_seek(from_current=-seek_amount*10**9) + if self.playlist: self.playlist.do_seek(from_current=-seek_amount*10**9) @dbus.service.method('org.panucci.panucciInterface') def forwardShort(self): self.__log.debug('forwardShort() called') seek_amount = self.gui.config.getint("options", "seek_short") - if self.player is not None: self.player.do_seek(from_current=seek_amount*10**9) + if self.playlist: self.playlist.do_seek(from_current=seek_amount*10**9) @dbus.service.method('org.panucci.panucciInterface') def backLong(self): self.__log.debug('backLong() called') seek_amount = self.gui.config.getint("options", "seek_long") - if self.player is not None: self.player.do_seek(from_current=-seek_amount*10**9) + if self.playlist: self.playlist.do_seek(from_current=-seek_amount*10**9) @dbus.service.method('org.panucci.panucciInterface') def forwardLong(self): self.__log.debug('forwardLong() called') seek_amount = self.gui.config.getint("options", "seek_long") - if self.player is not None: self.player.do_seek(from_current=seek_amount*10**9) + if self.playlist: self.playlist.do_seek(from_current=seek_amount*10**9) @dbus.service.method('org.panucci.panucciInterface', in_signature='s') def play_file(self, filepath): self.__log.debug('play_file() called with: ' + filepath) - if self.player is not None: - self.player.playlist.load(filepath) - self.player.play() + if self.playlist: + self.playlist.load(filepath) + self.playlist.play() @dbus.service.method('org.panucci.panucciInterface', in_signature='su') def playback_from(self, uri, seconds): @@ -131,44 +131,43 @@ class panucciInterface(dbus.service.Object): """ self.__log.debug('%s playback_from %d' % (uri, seconds)) - if self.player is not None: + if self.playlist: self.show_main_window() - new_file = (self.player.playlist.current_filepath != uri) + new_file = (self.playlist.current_filepath != uri) # I've diabled this for now as I've no possibility to debug it. /xerxes2 #if self.gui is not None: # self.gui.set_progress_indicator(new_file) if new_file: - self.player.playlist.load(uri) - self.player.playlist.last() - self.player.playlist.set_seek_to(seconds) - elif not self.player._is_playing: - self.player.play() + self.playlist.load(uri) + self.playlist.last() + self.playlist.set_seek_to(seconds) + elif not self.playlist.playing: + self.playlist.play_pause_toggle() #else: - # self.player.do_seek(from_beginning=(10**9)*seconds) + # self.playlist.do_seek(from_beginning=(10**9)*seconds) @dbus.service.method('org.panucci.panucciInterface', in_signature='s') def queue_file(self, filepath): self.__log.debug('queue_file() called with: ' + filepath) - if self.player is not None: self.player.playlist.append(filepath) + if self.playlist: self.playlist.load(filepath) @dbus.service.method('org.panucci.panucciInterface', in_signature='su') def insert_file(self, pos, filepath): self.__log.debug('insert_file() called') - if self.player is not None: self.player.playlist.insert(pos, filepath) + if self.playlist: self.playlist.insert(pos, filepath) @dbus.service.method('org.panucci.panucciInterface', in_signature='sb') def load_directory(self, directory, append): self.__log.debug('load_directory() called') - if self.player is not None: self.player.playlist.load_directory( - directory, append ) + if self.playlist: self.playlist.load_directory(directory, append) @dbus.service.method('org.panucci.panucciInterface') def show_main_window(self): self.__log.debug('show_main_window() called') - if self.gui is not None: self.gui.show_main_window() + if self.gui: self.gui.show_main_window() # Signals for gPodder's media player integration @dbus.service.signal(dbus_interface='org.gpodder.player', signature='us') diff --git a/src/panucci/gtkui/gtkmain.py b/src/panucci/gtkui/gtkmain.py index 4c077a9..12c793d 100644 --- a/src/panucci/gtkui/gtkmain.py +++ b/src/panucci/gtkui/gtkmain.py @@ -155,7 +155,7 @@ class PanucciGUI(object): # this should be done when the gui is ready self.playlist.init(filepath=filename) - pos_int, dur_int = self.playlist.player.get_position_duration() + pos_int, dur_int = self.playlist.get_position_duration() # This prevents bogus values from being set while seeking if (pos_int > 10**9) and (dur_int > 10**9): self.set_progress_callback(pos_int, dur_int) @@ -613,20 +613,20 @@ class PanucciGUI(object): def handle_headset_button(self, event, button): if event == 'ButtonPressed' and button == 'phone': - self.playlist.player.play_pause_toggle() + self.playlist.play_pause_toggle() def handle_connection_state(self, device_path): PATH = '/org/freedesktop/Hal/devices/computer_logicaldev_input_1' - if device_path == PATH and self.config.getboolean("options", "play_on_headset") and not self.playlist.player.playing: - self.playlist.player.play() + if device_path == PATH and self.config.getboolean("options", "play_on_headset") and not self.playlist.playing: + self.playlist.play_pause_toggle() def handle_bt_button(self, signal, button): # See http://bugs.maemo.org/8283 for details if signal == 'ButtonPressed': if button == 'play-cd': - self.playlist.player.play_pause_toggle() + self.playlist.play_pause_toggle() elif button == 'pause-cd': - self.playlist.player.pause() + self.playlist.play_pause_toggle() elif button == 'next-song': self.__player_tab.do_seek(self.config.getint("options", "seek_short")) elif button == 'previous-song': @@ -651,36 +651,23 @@ class PlayerTab(ObservableService, gtk.HBox): self.__gui_root = gui_root self.config = gui_root.config self.playlist = gui_root.playlist + self.metadata = None gtk.HBox.__init__(self) ObservableService.__init__(self, self.signals, self.__log) - # Timers self.progress_timer_id = None - self.recent_files = [] self.make_player_tab() self.has_coverart = False - #settings.register( 'enable_dual_action_btn_changed', - # self.on_dual_action_setting_changed ) - #settings.register( 'dual_action_button_delay_changed', - # self.on_dual_action_setting_changed ) - #settings.register( 'scrolling_labels_changed', lambda v: - # setattr( self.title_label, 'scrolling', v ) ) - - self.playlist.player.register( 'stopped', self.on_player_stopped ) - self.playlist.player.register( 'playing', self.on_player_playing ) - self.playlist.player.register( 'paused', self.on_player_paused ) - #self.playlist.player.register( 'eof', self.on_player_eof ) - self.playlist.register( 'end-of-playlist', - self.on_player_end_of_playlist ) - self.playlist.register( 'new-track-loaded', - self.on_player_new_track ) - self.playlist.register( 'new-metadata-available', - self.on_player_new_metadata ) - self.playlist.register( 'reset-playlist', - self.on_player_reset_playlist ) + self.playlist.register('stopped', self.on_player_stopped) + self.playlist.register('playing', self.on_player_playing) + self.playlist.register('paused', self.on_player_paused ) + self.playlist.register('end-of-playlist', self.on_player_end_of_playlist) + self.playlist.register('new-track-loaded', self.on_player_new_track) + self.playlist.register('new-metadata-available', self.on_player_new_metadata) + self.playlist.register('reset-playlist', self.on_player_reset_playlist ) def make_player_tab(self): main_vbox = gtk.VBox() @@ -783,7 +770,7 @@ class PlayerTab(ObservableService, gtk.HBox): self.bookmarks_button = create_da( gtkutil.generate_image('bookmark-new.png'), - self.playlist.player.add_bookmark_at_current_position, + self.playlist.add_bookmark_at_current_position, gtkutil.generate_image(gtk.STOCK_JUMP_TO, True), lambda *args: self.notify('select-current-item-request')) buttonbox.add(self.bookmarks_button) @@ -842,6 +829,9 @@ class PlayerTab(ObservableService, gtk.HBox): self.stop_progress_timer() self.set_controls_sensitivity(False) gtkutil.image(self.play_pause_button, 'media-playback-start.png') + if self.metadata: + estimated_length = self.metadata.get('length', 0) + self.set_progress_callback( 0, estimated_length ) def on_player_playing(self): self.start_progress_timer() @@ -862,12 +852,9 @@ class PlayerTab(ObservableService, gtk.HBox): def on_player_new_metadata(self): self.metadata = self.playlist.get_file_metadata() self.set_metadata(self.metadata) - - if not self.playlist.player.playing: - position = self.playlist.get_current_position() - estimated_length = self.metadata.get('length', 0) - self.set_progress_callback( position, estimated_length ) - self.playlist.player.set_position_duration(position, 0) + position = self.playlist.get_current_position() + estimated_length = self.metadata.get('length', 0) + self.set_progress_callback(position, estimated_length) def on_player_paused( self, position, duration ): self.stop_progress_timer() # This should save some power @@ -876,7 +863,6 @@ class PlayerTab(ObservableService, gtk.HBox): def on_player_end_of_playlist(self, loop): if not loop: - self.playlist.player.stop(False) estimated_length = self.metadata.get('length', 0) self.set_progress_callback( 0, estimated_length ) @@ -902,20 +888,20 @@ class PlayerTab(ObservableService, gtk.HBox): if ( not self.config.getboolean("options", "lock_progress") and event.type == gtk.gdk.BUTTON_PRESS and event.button == 1 ): new_fraction = event.x/float(widget.get_allocation().width) - resp = self.playlist.player.do_seek(percent=new_fraction) + resp = self.playlist.do_seek(percent=new_fraction) if resp: # Preemptively update the progressbar to make seeking smoother self.set_progress_callback( *resp ) def on_btn_play_pause_clicked(self, widget=None): - self.playlist.player.play_pause_toggle() + self.playlist.play_pause_toggle() def cover_art_callback(self, widget, event): self.on_btn_play_pause_clicked(widget) def progress_timer_callback( self ): - if self.playlist.player.playing and not self.playlist.player.seeking: - pos_int, dur_int = self.playlist.player.get_position_duration() + if self.playlist.playing and not self.playlist.seeking: + pos_int, dur_int = self.playlist.get_position_duration() # This prevents bogus values from being set while seeking if ( pos_int > 10**9 ) and ( dur_int > 10**9 ): self.set_progress_callback( pos_int, dur_int ) @@ -991,7 +977,7 @@ class PlayerTab(ObservableService, gtk.HBox): tags[tag].show() def do_seek(self, seek_amount): - resp = self.playlist.do_seek(seek_amount*10**9) + resp = self.playlist.do_seek(from_current=seek_amount*10**9) if resp: # Preemptively update the progressbar to make seeking smoother self.set_progress_callback( *resp ) diff --git a/src/panucci/player.py b/src/panucci/player.py index 4ca296c..18e74d7 100644 --- a/src/panucci/player.py +++ b/src/panucci/player.py @@ -32,11 +32,10 @@ class PanucciPlayer(ForwardingObservableService): """ signals = [ "playing", "paused", "stopped", "eof" ] - def __init__(self, playlist): + def __init__(self, config): self.__log = logging.getLogger('panucci.player.PanucciPlayer') ForwardingObservableService.__init__(self, self.signals, self.__log) - self.playlist = playlist - self.config = playlist.config + self.config = config if self.config.get("options", "backend") == "gstreamer": from panucci.backends.gstreamer import Player @@ -45,26 +44,19 @@ class PanucciPlayer(ForwardingObservableService): self.__initialized = False self._start_position = 0 self._is_playing = None + self.current_file = None # Forward the following signals self.forward( self.__player, [ "playing", "paused", "stopped", "eof" ], PanucciPlayer ) - self.__player.register( "playing", self.on_playing ) + #self.__player.register( "playing", self.on_playing ) self.__player.register( "paused", self.on_paused ) self.__player.register( "stopped", self.on_stopped ) self.__player.register( "eof", self.on_stopped ) self.__player.register( "error", self.on_player_error ) - self.playlist.register( 'new-track-loaded', self.on_new_track ) - self.playlist.register( 'seek-requested', self.do_seek ) - self.playlist.register( 'stop-requested', self.on_stop_requested ) - self.playlist.register( 'reset-playlist', self.on_reset_playlist ) - - # Register the d-bus interface only once we're ready - interface.register_player(self) - def __getattr__(self, attr): """ If the attribute isn't found in this object, get it from the player object. This makes proxying function calls simple @@ -77,35 +69,10 @@ class PanucciPlayer(ForwardingObservableService): self.__log.critical("No player available") raise AttributeError - def add_bookmark_at_current_position( self, label=None ): - """ Adds a bookmark at the current position - - Returns: (bookmark lable string, position in nanoseconds) - """ - - default_label, position = self.get_formatted_position() - label = default_label if label is None else label - self.playlist.save_bookmark( label, position ) - self.__log.info('Added bookmark: %s - %d', label, position) - return label, position - - def get_formatted_position(self, pos=None): - """ Gets the current position and converts it to a human-readable str. - - Returns: (bookmark lable string, position in nanoseconds) - """ - - if pos is None: - (pos, dur) = self.get_position_duration() - - text = util.convert_ns(pos) - return (text, pos) - - def on_new_track(self): + def on_new_track(self, filepath): """ New track callback; stops the player and starts the new track. """ - if self.playlist.current_filepath is not None: - filepath = self.playlist.current_filepath + if filepath: if filepath.startswith('/'): filepath = 'file://' + filepath @@ -117,6 +84,7 @@ class PanucciPlayer(ForwardingObservableService): if self.__initialized: self.play() + self.current_file = filepath self.__initialized = True def do_seek(self, from_beginning=None, from_current=None, percent=None): @@ -131,15 +99,14 @@ class PanucciPlayer(ForwardingObservableService): # Hand over the seek command to the backend return self.__player.do_seek(from_beginning, from_current, percent) - def on_playing(self): + def on_playing(self, seek_to): """ Used to seek to the correct position once the file has started playing. This has to be done once the player is ready because certain player backends can't seek in any state except playing. """ - seek = self.playlist.play() - if seek > 0: - self._seek(seek) + if seek_to > 0: + self._seek(seek_to) pos, dur = self.get_position_duration() pos_sec = pos / 10**9 @@ -164,12 +131,7 @@ class PanucciPlayer(ForwardingObservableService): if self.current_file is not None: interface.PlaybackStopped(self._start_position, pos_sec, dur_sec, self.current_file) - @property - def current_file(self): - return self.playlist.current_filepath - def on_stop_requested(self): - self.playlist.stop( self.get_position_duration()[0] ) self.stop() self._is_playing = False diff --git a/src/panucci/playlist.py b/src/panucci/playlist.py index ca6c815..8262d7a 100644 --- a/src/panucci/playlist.py +++ b/src/panucci/playlist.py @@ -26,6 +26,7 @@ import random import panucci from panucci.dbsqlite import db from panucci.services import ObservableService +from panucci.dbusinterface import interface from panucci import playlistformat from panucci import player from panucci import util @@ -40,7 +41,8 @@ def is_supported(uri): class Playlist(ObservableService): signals = [ 'new-track-loaded', 'new-metadata-available', 'file_queued', 'bookmark_added', 'seek-requested', 'end-of-playlist', - 'playlist-to-be-overwritten', 'stop-requested', 'reset-playlist' ] + 'playlist-to-be-overwritten', 'stop-requested', 'reset-playlist', + 'stopped', 'playing', 'paused' ] def __init__(self, config): self.__log = logging.getLogger('panucci.playlist.Playlist') @@ -49,11 +51,16 @@ class Playlist(ObservableService): self.filepath = panucci.PLAYLIST_FILE self._id = None - self.player = player.PanucciPlayer(self) - self.player.register( 'eof', self.on_player_eof ) + self.__player = player.PanucciPlayer(self.config) + self.__player.register('eof', self.on_player_eof) + self.__player.register('playing', self.on_player_playing) + self.__player.register('paused', self.on_player_paused) + self.__player.register('stopped', self.on_player_stopped) self.__queue = Queue(self.id) - self.__queue.register( - 'current_item_changed', self.on_queue_current_item_changed ) + self.__queue.register('current_item_changed', self.on_queue_current_item_changed) + + # Register the d-bus interface only once we're ready + interface.register_playlist(self) def init(self, filepath=None): """ Start playing the current file in the playlist or a custom file. @@ -66,16 +73,19 @@ class Playlist(ObservableService): if filepath is None or not self.load( filepath ): self.load_last_played() + self.set_position_duration(self.get_current_position(), 0) else: - self.player.play() + self.play() def reset_playlist(self): """ Sets the playlist to a default "known" state """ - self.notify('stop-requested', caller=self.reset_playlist) + #self.notify('stop-requested', caller=self.reset_playlist) + self.stop() self.filepath = None self._id = None self.__queue.clear() + self.reset_player() self.notify('reset-playlist', caller=self.reset_playlist) @property @@ -137,12 +147,12 @@ class Playlist(ObservableService): def on_queue_current_item_changed(self): if os.path.isfile(self.__queue.current_item.filepath): - self.notify( 'new-track-loaded', - caller=self.on_queue_current_item_changed ) - self.notify( 'new-metadata-available', - caller=self.on_queue_current_item_changed ) + self.new_track_loaded() + #self.notify( 'new-track-loaded', caller=self.on_queue_current_item_changed ) + #self.notify('new-metadata-available', caller=self.on_queue_current_item_changed) else: - self.player.notify( "eof", caller=self.on_queue_current_item_changed ) + #self.player.notify("eof", caller=self.on_queue_current_item_changed) + self.on_player_eof() def send_metadata(self): self.notify( 'new-metadata-available', caller=self.send_metadata ) @@ -157,9 +167,22 @@ class Playlist(ObservableService): if len(self.__queue.get_items()) - 1 == self.__queue.current_item_position: return True + def get_formatted_position(self, pos=None): + """ Gets the current position and converts it to a human-readable str. + + Returns: (bookmark lable string, position in nanoseconds) + """ + + if pos is None: + (pos, dur) = self.get_position_duration() + + text = util.convert_ns(pos) + return (text, pos) + def quit(self): self.__log.debug('quit() called.') - self.notify('stop-requested', caller=self.quit) + #self.notify('stop-requested', caller=self.quit) + self.stop() if self.__queue.modified: self.__log.info('Queue modified, saving temporary playlist') self.save_temp_playlist() @@ -168,27 +191,31 @@ class Playlist(ObservableService): # Bookmark-related functions ###################################### - def __load_from_bookmark( self, item_id, bookmark ): + def __load_from_bookmark(self, item_id, bookmark): new_pos = self.__queue.index(item_id) same_pos = self.__queue.current_item_position == new_pos - self.__queue.current_item_position = new_pos + if not same_pos: + self.__queue.current_item_position = new_pos if bookmark is None: - self.__queue.current_item.seek_to = 0 + seek_position = 0 else: - self.__queue.current_item.seek_to = bookmark.seek_position + seek_position = bookmark.seek_position + self.__queue.current_item.seek_to = seek_position - # if we don't request a seek nothing will happen - if same_pos: - self.notify( 'seek-requested', bookmark.seek_position, - caller=self.__load_from_bookmark ) + if same_pos: + if self.playing: + self.do_seek(seek_position) + else: + self.play() return True def load_from_bookmark_id( self, item_id=None, bookmark_id=None ): item, bookmark = self.__queue.get_bookmark(item_id, bookmark_id) if str(self.__queue.current_item) != item_id: - self.notify('stop-requested', caller=self.load_from_bookmark_id) + #self.notify('stop-requested', caller=self.load_from_bookmark_id) + self.stop() if item is not None: return self.__load_from_bookmark( str(item), bookmark ) @@ -270,11 +297,15 @@ class Playlist(ObservableService): if bookmark_id is None: if self.__queue.current_item_position == self.__queue.index(item): + is_end = self.end_of_playlist() + self.stop() item.delete_bookmark(None) self.__queue.remove(item) if self.__queue.current_item_position != 0: - self.notify('stop-requested', caller=self.remove_bookmark) - self.__queue.current_item_position = self.__queue.current_item_position + if not is_end: + self.__queue.current_item_position = self.__queue.current_item_position + else: + self.__queue.current_item_position = 0 else: self.reset_playlist() else: @@ -298,6 +329,17 @@ class Playlist(ObservableService): else: return self.remove_bookmark( item_id, bookmark_id ) + def add_bookmark_at_current_position( self, label=None ): + """ Adds a bookmark at the current position + Returns: (bookmark lable string, position in nanoseconds) + """ + + default_label, position = self.get_formatted_position() + label = default_label if label is None else label + self.save_bookmark( label, position ) + self.__log.info('Added bookmark: %s - %d', label, position) + return label, position + def move_item( self, from_row, to_row ): self.__log.info('Moving item from position %d to %d', from_row, to_row) assert isinstance(from_row, int) and isinstance(to_row, int) @@ -380,7 +422,7 @@ class Playlist(ObservableService): """ Detects filepath's filetype then loads it using the appropriate loader function """ self.__log.debug('Attempting to load %s', filepath) - _play = self.__queue.is_empty() or (self.end_of_playlist() and self.player._is_playing == None) + _play = self.__queue.is_empty() or (self.end_of_playlist() and self.null) if self.__queue.is_empty(): _position = 0 else: @@ -416,9 +458,11 @@ class Playlist(ObservableService): self.load_from_resume_bookmark() self.__queue.disable_notifications = False self.__queue.modified = True - self.notify( 'stop-requested', caller=self.load ) - self.notify( 'new-track-loaded', caller=self.load ) - self.notify( 'new-metadata-available', caller=self.load ) + #self.notify( 'stop-requested', caller=self.load ) + self.stop() + self.new_track_loaded() + #self.notify( 'new-track-loaded', caller=self.load ) + #self.notify( 'new-metadata-available', caller=self.load ) return not error @@ -491,24 +535,12 @@ class Playlist(ObservableService): """Set the seek-to position for the current track""" self.__queue.current_item.seek_to = (10**9) * position - def play(self): - """ This gets called by the player to get - the last time the file was paused """ - pos = self.__queue.current_item.seek_to - self.__queue.current_item.seek_to = 0 - return pos - - def pause(self, position): - """ Called whenever the player is paused """ - self.__queue.current_item.seek_to = position - - def stop(self, position, save_resume_point=True): - """ This should be run when the program is closed - or if the user switches playlists """ - self.remove_resume_bookmarks() - if not self.is_empty and save_resume_point: - self.__queue.current_item.save_bookmark( - _('Auto Bookmark'), position, True ) + def get_seek_to(self, reset=True): + """Get the seek-to position for the current track""" + seek_to = self.__queue.current_item.seek_to + if reset: + self.__queue.current_item.seek_to = 0 + return seek_to def skip(self, loop=True, skip_by=None, skip_to=None): """ Skip to another track in the playlist. @@ -521,10 +553,10 @@ class Playlist(ObservableService): if not self.__queue: return False - current_item = self.__queue.current_item_position + current_item_position = self.__queue.current_item_position if skip_by is not None: - skip = current_item + skip_by + skip = current_item_position + skip_by elif skip_to is not None: skip = skip_to else: @@ -532,7 +564,7 @@ class Playlist(ObservableService): self.__log.warning('No skip method provided...') if not 0 <= skip < self.queue_length: - self.notify( 'end-of-playlist', loop, caller=self.skip ) + #self.notify( 'end-of-playlist', loop, caller=self.skip ) if not loop: self.__log.warning( "Can't skip to non-existant file w/o loop." @@ -551,7 +583,8 @@ class Playlist(ObservableService): else: skip = 0 - self.notify('stop-requested', caller=self.skip) + #self.notify('stop-requested', caller=self.skip) + self.stop() self.__queue.current_item_position = skip self.__log.debug( 'Skipping to file %d (%s)', skip, self.__queue.current_item.filepath ) @@ -580,7 +613,38 @@ class Playlist(ObservableService): skip_to = random.choice(range(len(self.__queue.get_items()))) return self.skip( False, None, skip_to ) + ################################## + # Player controls + ################################## + + def play(self): + """ This gets called by the player to get + the last time the file was paused """ + self.__player.play() + + def pause(self): + """ Called whenever the player is paused """ + #position = self.get_position_duration()[0] + #self.__queue.current_item.seek_to = position + self.__player.pause() + + def play_pause_toggle(self): + if self.current_filepath: + self.__player.play_pause_toggle() + + def stop(self, save_resume_point=True): + """ This should be run when the program is closed + or if the user switches playlists """ + position = self.get_position_duration()[0] + self.remove_resume_bookmarks() + self.on_player_stopped() + self.__player.on_stop_requested() + if not self.is_empty and save_resume_point: + self.__queue.current_item.save_bookmark(_('Auto Bookmark'), position, True) + def on_player_eof(self): + if not self.config.getboolean("options", "stay_at_end"): + self.stop() play_mode = self.config.get("options", "play_mode") if play_mode == "single": if not self.config.getboolean("options", "stay_at_end"): @@ -596,20 +660,69 @@ class Playlist(ObservableService): else: self.next(False) - def do_seek(self, seek_amount): + def on_player_playing(self): + self.notify("playing", caller=self.on_player_playing) + self.__player.on_playing(self.get_seek_to()) + + def on_player_paused(self, position, duration): + self.notify("paused", position, duration, caller=self.on_player_paused) + + def on_player_stopped(self): + self.notify("stopped", caller=self.on_player_stopped) + + def do_seek(self, from_beginning=None, from_current=None, percent=None): resp = None - if not self.config.getboolean("options", "seek_back") or self.start_of_playlist() or seek_amount > 0: - resp = self.player.do_seek(from_current=seek_amount) - else: - pos_int, dur_int = self.player.get_position_duration() - if pos_int + seek_amount >= 0: - resp = self.player.do_seek(from_current=seek_amount) + if from_beginning: + resp = self.__player.do_seek(from_beginning=from_beginning) + elif from_current: + if not self.config.getboolean("options", "seek_back") or self.start_of_playlist() or from_current > 0: + resp = self.__player.do_seek(from_current=from_current) else: - self.prev() - pos_int, dur_int = self.player.get_position_duration() - resp = self.player.do_seek(from_beginning=dur_int+seek_amount) + pos_int, dur_int = self.get_position_duration() + if pos_int + from_current >= 0: + resp = self.__player.do_seek(from_current=from_current) + else: + self.prev() + pos_int, dur_int = self.get_position_duration() + resp = self.__player.do_seek(from_beginning=dur_int+from_current) + elif percent: + resp = self.__player.do_seek(percent=percent) return resp + def get_position_duration(self): + return self.__player.get_position_duration() + + def set_position_duration(self, position, duration): + self.__player.set_position_duration(position, duration) + + @property + def playing(self): + return self.__player.playing + + @property + def paused(self): + return self.__player.paused + + @property + def stopped(self): + return self.__player.stopped + + @property + def null(self): + return self.__player.null + + @property + def seeking(self): + return self.__player.seeking + + def new_track_loaded(self): + self.__player.on_new_track(self.current_filepath) + self.notify('new-track-loaded', caller=self.new_track_loaded) + self.notify( 'new-metadata-available', caller=self.new_track_loaded ) + + def reset_player(self): + self.__player.on_reset_playlist() + class Queue(list, ObservableService): """ A Simple list of PlaylistItems """ diff --git a/src/panucci/qtui/qtmain.py b/src/panucci/qtui/qtmain.py index a950cde..ddc568d 100644 --- a/src/panucci/qtui/qtmain.py +++ b/src/panucci/qtui/qtmain.py @@ -325,11 +325,11 @@ class PlayerTab(ObservableService): self.config = gui_root.config self.playlist = gui_root.playlist ObservableService.__init__(self, self.signals, self.__log) + self.metadata = None - self.playlist.player.register( 'stopped', self.on_player_stopped ) - self.playlist.player.register( 'playing', self.on_player_playing ) - self.playlist.player.register( 'paused', self.on_player_paused ) - #self.playlist.player.register( 'eof', self.on_player_eof ) + self.playlist.register( 'stopped', self.on_player_stopped ) + self.playlist.register( 'playing', self.on_player_playing ) + self.playlist.register( 'paused', self.on_player_paused ) self.playlist.register( 'end-of-playlist', self.on_player_end_of_playlist ) self.playlist.register( 'new-track-loaded', self.on_player_new_track ) self.playlist.register( 'new-metadata-available', self.on_player_new_metadata ) @@ -417,13 +417,16 @@ class PlayerTab(ObservableService): self.mainbox.addLayout(layout) self.timer = QtCore.QTimer() - self.timer.setInterval(1000); + self.timer.setInterval(1000) self.timer.timeout.connect(self.timer_callback) def on_player_stopped(self): self.stop_progress_timer() - #self.set_controls_sensitivity(False) self.button_play.setIcon(self.icon_play) + if self.metadata: + estimated_length = self.metadata.get('length', 0) + self.set_progress_callback( 0, estimated_length ) + #self.set_controls_sensitivity(False) def on_player_playing(self): self.start_progress_timer() @@ -432,8 +435,8 @@ class PlayerTab(ObservableService): def on_player_paused( self, position, duration ): self.stop_progress_timer() - #self.set_progress_callback( position, duration ) self.button_play.setIcon(self.icon_play) + #self.set_progress_callback( position, duration ) def on_player_new_track(self): for widget in [self.label_title, self.label_artist, self.label_album, self.label_cover]: @@ -444,16 +447,12 @@ class PlayerTab(ObservableService): def on_player_new_metadata(self): self.metadata = self.playlist.get_file_metadata() self.set_metadata(self.metadata) - - if not self.playlist.player.playing: - position = self.playlist.get_current_position() - estimated_length = self.metadata.get('length', 0) - self.set_progress_callback( position, estimated_length ) - self.playlist.player.set_position_duration(position, 0) + position = self.playlist.get_current_position() + estimated_length = self.metadata.get('length', 0) + self.set_progress_callback(position, estimated_length) def on_player_end_of_playlist(self, loop): if not loop: - self.playlist.player.stop(False) estimated_length = self.metadata.get('length', 0) self.set_progress_callback( 0, estimated_length ) @@ -473,7 +472,7 @@ class PlayerTab(ObservableService): self.do_seek(-1*self.config.getint("options", "seek_short")) def button_play_callback(self): - self.playlist.player.play_pause_toggle() + self.playlist.play_pause_toggle() def button_forward_callback(self): self.do_seek(self.config.getint("options", "seek_short")) @@ -482,15 +481,14 @@ class PlayerTab(ObservableService): self.do_seek(self.config.getint("options", "seek_long")) def button_bookmark_callback(self): - self.playlist.player.add_bookmark_at_current_position() + self.playlist.add_bookmark_at_current_position() def label_cover_callback(self, event): - self.playlist.player.play_pause_toggle() + self.playlist.play_pause_toggle() def set_progress_callback(self, time_elapsed, total_time): """ times must be in nanoseconds """ - time_string = "%s / %s" % ( util.convert_ns(time_elapsed), - util.convert_ns(total_time) ) + time_string = "%s / %s" %(util.convert_ns(time_elapsed), util.convert_ns(total_time)) self.progress.setFormat( time_string ) fraction = float(time_elapsed) / float(total_time) if total_time else 0 self.progress.setValue( int(fraction*100) ) @@ -499,14 +497,14 @@ class PlayerTab(ObservableService): if ( not self.config.getboolean("options", "lock_progress") and event.button() == QtCore.Qt.MouseButton.LeftButton ): new_fraction = float(event.x())/float(self.progress.width()) - resp = self.playlist.player.do_seek(percent=new_fraction) + resp = self.playlist.do_seek(percent=new_fraction) if resp: # Preemptively update the progressbar to make seeking smoother self.set_progress_callback( *resp ) def timer_callback( self ): - if self.playlist.player.playing and not self.playlist.player.seeking: - pos_int, dur_int = self.playlist.player.get_position_duration() + if self.playlist.playing and not self.playlist.seeking: + pos_int, dur_int = self.playlist.get_position_duration() # This prevents bogus values from being set while seeking if ( pos_int > 10**9 ) and ( dur_int > 10**9 ): self.set_progress_callback( pos_int, dur_int ) @@ -578,7 +576,7 @@ class PlayerTab(ObservableService): tags[tag].show() def do_seek(self, seek_amount): - resp = self.playlist.do_seek(seek_amount*10**9) + resp = self.playlist.do_seek(from_current=seek_amount*10**9) if resp: # Preemptively update the progressbar to make seeking smoother self.set_progress_callback( *resp ) -- 2.11.4.GIT