player.py

Go to the documentation of this file.
00001 from __future__ import with_statement
00002 
00003 from plai.playlist import Playlist
00004 from plai.utility import PrettyPrintTime, GreyedOut, ReportError
00005 from plai.collectiontree import CollectionTree
00006 from plai import config
00007 
00008 import gtk,gobject,gst
00009 import os,copy,pickle           
00010 import gtk.glade
00011 
00012 from plai import config 
00013 
00014 
00015 class Player:
00016         """The main player. Handles the collection tree and playlists. Manages
00017         GUI not associated with these."""
00018         def __init__(self):
00019                 xml=gtk.glade.XML(config.gladefname)
00020                 handlers={'on_mainwindow_destroy'                                               : self._onQuit_onQuit,
00021                                   'on_SearchBox_changed'                                                : self._onSearch_onSearch,
00022                                   'on_SearchBox_activate'                                               : self._onSearch_onSearch,
00023                                   'on_SearchResults_row_activated'                              : self._onSearchActivate_onSearchActivate,
00024                                   'on_AddButton_clicked'                                                : self._onAdd_onAdd,
00025                                   'on_PauseButton_toggled'                                              : self._onPause_onPause,
00026                                   'on_RemoveButton_clicked'                                             : self._onRemove_onRemove,
00027                                   'on_VolumeSlider_value_changed'                               : self._onVolume_onVolume,
00028                                   'on_SongProgressEventBox_button_press_event'  : self._onSeek_onSeek,
00029                                   'on_ShuffleButton_clicked'                                    : self._onShuffle_onShuffle,
00030                                   'on_NewButton_clicked'                                                : self._onNew_onNew,
00031                                   'on_notebook_key_press_event'                                 : self._onNotebookKey_onNotebookKey}
00032                 xml.signal_autoconnect(handlers)
00033                 
00034                 self._notebook_notebook=xml.get_widget('notebook')
00035                 self._volslider_volslider=xml.get_widget('VolumeSlider')
00036                 self._pausebutton_pausebutton=xml.get_widget('PauseButton')
00037                 self._searchbox_searchbox=xml.get_widget('SearchBox')
00038                 self._songprogress_songprogress=xml.get_widget('SongProgress')
00039                 
00040                 self._searchresults_searchresults=CollectionTree(xml.get_widget('SearchResults'))
00041                 
00042                 gobject.timeout_add(1000,self._onTimer_onTimer,None)    #TODO - turn this off when not needed, careful not to have more than one at once...
00043                 
00044                 self._playbin_playbin=gst.element_factory_make('playbin','myplayer')
00045                 bus=self._playbin_playbin.get_bus()
00046                 bus.add_watch(self._onBus_onBus,None)
00047                 
00048                 self._tonotify_tonotify=[]
00049                 
00050                 self._playingplaylist_playingplaylist=None
00051                 
00052                 self._lasttypingtime_lasttypingtime=0
00053                 
00054                 self._mainwin_mainwin=xml.get_widget('MainWindow')
00055                 self._mainwin_mainwin.maximize()
00056                 self._mainwin_mainwin.show()
00057                 # The window won't be ready until SetState is finished
00058                 self._mainwin_mainwin.set_sensitive(False)      
00059                 
00060                 
00061         def RegisterForNotifications(self,func):
00062                 """Add a function to the list to call when the state changes."""
00063                 self._tonotify_tonotify.append(func)
00064                 
00065                 
00066         def _SendNotification(self):
00067                 """Private. Dispatch notifications. Call me whenever you change
00068                 player state and think someone might be interested."""
00069                 for f in self._tonotify_tonotify: f()
00070                 
00071                 
00072         def GetState(self):
00073                 """Returns a representation of the player state suitable for giving
00074                 to SetState() thus allowing persistance.
00075                 Format is (currenttab,[(label,playliststate)],volume).
00076                 TODO - make the bits that peek at private Playlist stuff be a function on Playlist."""
00077                 if self._playingplaylist_playingplaylist:
00078                         curtab=self._notebook_notebook.page_num(self._playingplaylist_playingplaylist._view.get_data('Scroller'))
00079                 else:
00080                         curtab=self._notebook_notebook.get_current_page()
00081                 
00082                 playlists=[]
00083                 for n in range(self._notebook_notebook.get_n_pages()):          
00084                         page=self._notebook_notebook.get_nth_page(n)
00085                         label=self.GetPageLabelGetPageLabel(page).get_text()
00086                         pl=page.get_data('PlaylistObject')
00087                         plstate=pl.GetState()
00088                         playlists.append((label,plstate))
00089                         
00090                 vol=self._volslider_volslider.get_value()
00091                 
00092                 return (curtab,playlists,vol)
00093                 
00094                 
00095         def SetState(self,state):
00096                 """Sets the player state to that represented by 'state' as
00097                 produced by GetState()."""
00098                 
00099                 def inittab(n):
00100                         page=self._notebook_notebook.get_nth_page(n)
00101                         pl=page.get_data('PlaylistObject')
00102                         (label,plstate)=playlists[n]
00103                         pl.SetState(plstate)
00104                         
00105                 (curtab,playlists,vol)=state
00106                 self._volslider_volslider.set_value(vol)
00107                 
00108                 for pl in playlists:
00109                         self._onNew_onNew(None)
00110                         (label,plstate)=pl
00111                         page=self._notebook_notebook.get_nth_page(self._notebook_notebook.get_current_page())
00112                         self.GetPageLabelGetPageLabel(page).set_text(label)
00113                         # The playlist is not fully constructed yet. Grey it out.
00114                         # This will be undone when SetState is called.
00115                         page.get_data('PlaylistObject')._view.set_sensitive(False)
00116                 
00117                 # We need to do this one now because the current track is required to
00118                 # exist when we start playing below.
00119                 inittab(curtab)
00120                 # Initialise the rest of the tabs lazily: left to right.
00121                 for n in range(len(playlists)):
00122                         if n!=curtab: gobject.idle_add(inittab,n)
00123                                         
00124                 self._notebook_notebook.set_current_page(curtab)
00125                 page=self._notebook_notebook.get_nth_page(self._notebook_notebook.get_current_page())
00126                 self._playingplaylist_playingplaylist=page.get_data('PlaylistObject')
00127                 self.StartPlayingStartPlaying()
00128                 self.PausePause()
00129                 
00130                 self._mainwin_mainwin.set_sensitive(True)
00131                 
00132         
00133         def GetPlayingPlaylist(self):
00134                 """Returns the playlist containing the currently playing song."""
00135                 return self._playingplaylist_playingplaylist            
00136                 
00137                 
00138         def GotoNextTrack(self):
00139                 """Move to the next track in the current playlist and start playing it
00140                 if possible. Otherwise pause the player."""
00141                 if self.GetPlayingPlaylistGetPlayingPlaylist().GotoNextTrack():
00142                         self.GetPlayingPlaylistGetPlayingPlaylist().ScrollToCurrent()
00143                         self.StartPlayingStartPlaying()
00144                         self._SendNotification_SendNotification()
00145                 else:
00146                         self.PausePause()
00147                 
00148                 
00149         def _onBus(self,bus,msg,data):
00150                 """Callback. Handles messages from the gstreamer bus.
00151                 Responds to EOS by proceeding to next track."""
00152                 if msg.type==gst.MESSAGE_EOS:
00153                         self.GotoNextTrackGotoNextTrack()
00154                         return True
00155                 elif msg.type==gst.MESSAGE_ERROR:
00156                         ReportError('GStreamer Error',msg.parse_error()[1])
00157                         
00158                         return True
00159                 else:
00160                         #got some other message...
00161                         return True             
00162                 
00163                 
00164         def _onSearch(self,widget):
00165                 """Private callback."""
00166 #               with GreyedOut(widget):
00167                 self.RefreshSearchRefreshSearch()
00168                 
00169 
00170         def RefreshSearch(self):
00171                 """Refreshes the search results tree using whatever
00172                    is in the search box."""
00173                 self._searchresults_searchresults.RunQuery(self._searchbox_searchbox.get_text())
00174                                 
00175                                 
00176         def StartPlaying(self):
00177                 """Starts playing the current track if it exists."""
00178                 self._pausebutton_pausebutton.set_active(False)
00179                 
00180                 self._playbin_playbin.set_state(gst.STATE_NULL)
00181                 if self._playingplaylist_playingplaylist.GetCurrentTrack():
00182                         self._playbin_playbin.set_property('uri','file://'+self._playingplaylist_playingplaylist.GetCurrentTrack().Filename())
00183                         self._playbin_playbin.set_state(gst.STATE_PLAYING)
00184                         
00185                 self._SendNotification_SendNotification()
00186                 
00187                 
00188         def _onPlaylistActivate(self,widget,path,column):
00189                 """Callback. Skips to the selected item in the current playlist."""
00190                 it=widget.get_model().get_iter(path)
00191                 self.GetCurrentPlaylistGetCurrentPlaylist().SetCurrentTrack(it)
00192                 self._playingplaylist_playingplaylist=self.GetCurrentPlaylistGetCurrentPlaylist()
00193                 self.StartPlayingStartPlaying()
00194                 
00195                 
00196         def _onSearchActivate(self,widget,path,column):
00197                 """Callback. Appends selected search results to the current playlist
00198                 and starts playing the first."""
00199                 self.GetCurrentPlaylistGetCurrentPlaylist().SetCurrentTrack(self.AppendSearchSelectionToPlaylistAppendSearchSelectionToPlaylist())
00200                 self.StartPlayingStartPlaying()
00201                 
00202 
00203         def AppendSearchSelectionToPlaylist(self):
00204                 """Appends the selected search results to the current playlist.
00205                 Return an iterator to the first track appended."""
00206                 tracks=self._searchresults_searchresults.GetSelectedTracks()
00207                 tracks=[copy.copy(t) for t in tracks]
00208                 return self.GetCurrentPlaylistGetCurrentPlaylist().AppendTracks(tracks)
00209                         
00210                         
00211         def _onAdd(self,widget):
00212                 """Callback for Add button."""
00213                 self.AppendSearchSelectionToPlaylistAppendSearchSelectionToPlaylist()           
00214 
00215         
00216         def Pause(self):
00217                 """Pause the currently playing track."""
00218                 self._playbin_playbin.set_state(gst.STATE_PAUSED)
00219                 self._pausebutton_pausebutton.set_active(True)          
00220                 
00221                 self._SendNotification_SendNotification()
00222                 
00223                 
00224         def UnPause(self):
00225                 """Resume the currently playing track."""
00226                 self._playbin_playbin.set_state(gst.STATE_PLAYING)              
00227                 self._pausebutton_pausebutton.set_active(False)
00228                 
00229                 self._SendNotification_SendNotification()
00230                 
00231                 
00232         def IsPaused(self):
00233                 """Returns True if the current track is paused."""
00234                 return self._pausebutton_pausebutton.get_active()
00235                 
00236                 
00237         def _onPause(self,widget):
00238                 """Callback for the Pause button. Toggles whether the current song
00239                 is paused based on what the button says."""
00240                 if widget.get_active():
00241                         self.PausePause()
00242                 else:
00243                         self.UnPauseUnPause()
00244                 
00245                 
00246         def _onVolume(self,widget):
00247                 """Callback for the volume slider. Sets the gstreamer volume
00248                 to what the slider says."""
00249                 self._playbin_playbin.set_property('volume',.01*widget.get_value())
00250 
00251 
00252         def SaveState(self):
00253                 """Writes out GetState() to 'config.picklefname'"""
00254                 print 'Saving state',
00255                 with file(config.picklefname,'w') as f:
00256                         pickle.dump(self.GetStateGetState(),f,pickle.HIGHEST_PROTOCOL)
00257                 print ' - Done' 
00258                 
00259         def _onQuit(self,widget):
00260                 """Callback for the close button. Saves state and calls gtk.main_quit()."""
00261                 self.PausePause()
00262                 self.SaveStateSaveState()
00263                 gtk.main_quit()
00264                 
00265                 
00266         def GetSongPosAndLength(self):
00267                 """Returns the position in and length of the current track
00268                 in seconds as a tuple."""
00269                 try:
00270                         (pos,junk)=self._playbin_playbin.query_position(gst.FORMAT_TIME)
00271                         (length,junk)=self._playbin_playbin.query_duration(gst.FORMAT_TIME)
00272                         if pos<0: pos=0
00273                         if pos>length: pos=length
00274                         return (int(pos/1e9),int(length/1e9))
00275                 except:
00276                         return (0,1)
00277                         
00278                 
00279         def _onTimer(self,data):
00280                 """Callback. Every second update the progress bar."""
00281                 (pos,length)=self.GetSongPosAndLengthGetSongPosAndLength()
00282                         
00283                 prog=self._songprogress_songprogress
00284                 if(prog!=None):
00285                         prog.set_fraction(float(pos)/length)
00286                         prog.set_text(PrettyPrintTime(pos)+'/'+PrettyPrintTime(length))
00287                         
00288                 self._SendNotification_SendNotification()
00289                 
00290                 return True             #we want more of these events
00291         
00292         
00293         def _onRemove(self,widget):
00294                 """Callback for the Remove button. Removes the selected tracks
00295                 from the current playlist."""
00296                 wasplaying=self.GetCurrentPlaylistGetCurrentPlaylist().RemoveSelected()
00297                 if wasplaying:
00298                         self._playbin_playbin.set_state(gst.STATE_NULL)
00299                         self.GetCurrentPlaylistGetCurrentPlaylist().SetCurrentTrack(None)
00300         
00301         
00302         def _onSeek(self,widget,event):
00303                 """Callback for clicking on the song progress bar. Seeks to 
00304                 the position in the stream that the user clicked."""
00305                 (x,y)=widget.window.get_size()
00306                 prog=float(event.x)/x;
00307                 (length,junk)=self._playbin_playbin.query_duration(gst.FORMAT_TIME)
00308                 length=length/1e9
00309                 self._playbin_playbin.seek(1.0,gst.FORMAT_TIME,gst.SEEK_FLAG_FLUSH,gst.SEEK_TYPE_SET,prog*length*gst.SECOND,gst.SEEK_TYPE_NONE,0)                       
00310                 self._songprogress_songprogress.set_fraction(prog)
00311 
00312                 
00313         def _onShuffle(self,widget):
00314                 """Callback for the Shuffle button. Shuffle the current playlist."""
00315                 self.GetCurrentPlaylistGetCurrentPlaylist().Shuffle()
00316                 
00317         
00318         def GetCurrentTrack(self):
00319                 """Returns the current track or None if there isn't one."""
00320                 pl=self.GetPlayingPlaylistGetPlayingPlaylist()
00321                 return pl.GetCurrentTrack() if pl else None
00322                 
00323         
00324         def _onNew(self,widget):
00325                 """Callback for the New button. Adds a new notebook page,
00326                 and adds the playlist object as data on the page."""
00327                 xml=gtk.glade.XML(config.gladeplaylistfname)
00328                 handlers={'on_Playlist_row_activated' : self._onPlaylistActivate_onPlaylistActivate,
00329                                   'on_Close_clicked'              : self._onCloseTab_onCloseTab}
00330                 xml.signal_autoconnect(handlers)
00331                 num=self._notebook_notebook.append_page(xml.get_widget('PlaylistScroller'),             
00332                                                                                 xml.get_widget('PlaylistTitle'))
00333                 xml.get_widget('Playlist').set_data('Scroller',xml.get_widget('PlaylistScroller'))                                                                              
00334                 pl=Playlist(xml.get_widget('Playlist'))
00335                 xml.get_widget('Close').set_data('ControlledTab',xml.get_widget('PlaylistScroller'))
00336                 self._notebook_notebook.get_nth_page(num).set_data('PlaylistObject',pl)
00337                 self._notebook_notebook.set_current_page(-1)
00338                 
00339                 
00340         def GetCurrentPlaylist(self):
00341                 """Returns the Playlist object associated with the currently focussed tab."""
00342                 page=self._notebook_notebook.get_nth_page(self._notebook_notebook.get_current_page())
00343                 return page.get_data('PlaylistObject') if page else None
00344                 
00345                 
00346         def _onCloseTab(self,widget):
00347                 """Callback for notebook page close buttons.
00348                 Removes the tab in whose title 'widget' is."""
00349                 tab=widget.get_data('ControlledTab')
00350                 
00351                 # Stop playing the current song if it lived in this tab. Otherwise
00352                 # confusion might ensue.
00353                 if self._playingplaylist_playingplaylist is tab.get_data('PlaylistObject'):
00354                         self._playbin_playbin.set_state(gst.STATE_NULL)
00355                 
00356                 self._notebook_notebook.remove_page(self._notebook_notebook.page_num(tab))
00357         
00358         
00359         def GetPageLabel(self,page):
00360                 """Returns the gtk.Label for 'page' or None."""
00361                 return self._notebook_notebook.get_tab_label(page).get_children()[0] if page else None
00362                 
00363                 
00364         def _onNotebookKey(self,widget,event):
00365                 """Private callback for typing in the notebook. Modified the current
00366                 page's title."""
00367                 if event.string=='': return
00368                 label=self.GetPageLabelGetPageLabel(self._notebook_notebook.get_nth_page(self._notebook_notebook.get_current_page()))
00369                 if label:
00370                         if event.time-self._lasttypingtime_lasttypingtime>config.notebooktitletimeout:
00371                                 label.set_text('')
00372                         self._lasttypingtime_lasttypingtime=event.time
00373                         label.set_text(label.get_text()+event.string)
00374                 

Generated on Mon Aug 6 21:24:20 2007 for plai by  doxygen 1.5.1