from PyQt4 import QtGui, QtSvg, QtCore from misc import * from clMonty import monty from clPlugin import * import clSong from thread import start_new_thread from random import randint import plugins # Some predefined constants. # Note that REPEAT precedes RANDOM. E.g. if repeat # is ALBUM, and random is SONG, then it'll take a random song # from the current album ... PC_RANDOM_NO=0 # no randomness PC_RANDOM_SONG=1 # choose a song random from the playlist PC_RANDOM_ALBUM=2 # choose a random album, and play that fully PC_RANDOM_QUEUE=3 # choose next song from the queue PC_REPEAT_NO=0 # no repeat PC_REPEAT_SONG=1 # repeat current song PC_REPEAT_ALBUM=2 # repeat current album PC_REPEAT_PLAYLIST=3 # repeat playlist PLAYCONTROL_SHUFFLE_DEFAULT=PC_RANDOM_ALBUM PLAYCONTROL_OLDSHUFFLE_DEFAULT=PLAYCONTROL_SHUFFLE_DEFAULT PLAYCONTROL_QUEUE_DEFAULT='' PLAYCONTROL_REPEAT_DEFAULT=PC_REPEAT_PLAYLIST class wgPlayControl(QtGui.QWidget): """Displays controls for interacting with playing, like play, volume ...""" " control buttons" btnPlayPause=None btnStop=None btnPrevious=None btnNext=None " button to jump to current song" btnJmpCurrent=None " slider for current time" slrTime=None " slider for volume" slrVolume=None " indicator for volume" svgVolume=None " all objects in this widget" objects=None cmbRepeat=None cmbShuffle=None p=None " contains the songs of the album the current song is playing. None, if the album is not set" curAlbumSongs=None " queued songs: int*" queuedSongs=[] # what mode where we in before the queue started? beforeQueuedMode=None def __init__(self, p, parent=None): QtGui.QWidget.__init__(self, parent) self.p=p class wgSvgSwitcher(QtSvg.QSvgWidget): """Widget showing an svg-image, which, when clicked, will (un)hide an element.""" # the element we wish to hide/show scroller=None def __init__(self,scroller,parent=None): QtSvg.QSvgWidget.__init__(self,parent) self.scroller=scroller def mousePressEvent(self,event): self.scroller.setVisible(not self.scroller.isVisible()) def wheelEvent(self, event): event.accept() numDegrees=event.delta() / 8 numSteps=5*numDegrees/15 self.scroller.setValue(self.scroller.value()+numSteps) self.slrTime=QtGui.QSlider(QtCore.Qt.Horizontal, self) self.slrTime.setMinimumWidth(100) self.slrVolume=QtGui.QSlider(QtCore.Qt.Vertical, self) self.slrVolume.setMaximum(100) self.slrVolume.setMinimumWidth(100) self.slrVolume.setMaximumWidth(350) # set to some value that'll never be chosen, that way onChange will be called automatically :) self.slrVolume.setValue(3.141595) self.slrVolume.setVisible(False) self.svgVolume=wgSvgSwitcher(self.slrVolume) self.svgVolume.setMaximumSize(64,64) self.svgVolume.setMinimumSize(64,64) self.btnPlayPause=Button("play", self.onBtnPlayPauseClick, 'gfx/media-playback-start.svg', True) self.btnStop=Button("stop", self.onBtnStopClick, 'gfx/media-playback-stop.svg', True) self.btnPrevious=Button("prev", self.onBtnPreviousClick, 'gfx/media-skip-backward.svg', True) self.btnNext=Button("next", self.onBtnNextClick, 'gfx/media-skip-forward.svg', True) self.btnJmpCurrent=Button("Current", self.onBtnJmpCurrentClick) self.cmbShuffle=QtGui.QComboBox(self) self.cmbShuffle.addItem('Don\'t play dices') self.cmbShuffle.addItem('Random song') self.cmbShuffle.addItem('Random album') self.cmbShuffle.addItem('Queue') self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle'))) self.cmbRepeat=QtGui.QComboBox(self) self.cmbRepeat.addItem('No repeat') self.cmbRepeat.addItem('Repeat current song') self.cmbRepeat.addItem('Repeat album') self.cmbRepeat.addItem('Playlist') self.cmbRepeat.setCurrentIndex(int(self.p.getSetting('repeat'))) self.objects=[self.slrVolume, self.slrTime, self.btnStop, self.btnNext, self.btnPrevious] layout=QtGui.QHBoxLayout(parent) layout2=QtGui.QHBoxLayout(parent) layoutWidget=QtGui.QVBoxLayout(parent) layoutWidget.addLayout(layout) layoutWidget.addLayout(layout2) self.setLayout(layoutWidget) layout.addWidget(self.btnPrevious) layout.addWidget(self.btnPlayPause) layout.addWidget(self.btnStop) layout.addWidget(self.btnNext) layout.addWidget(self.slrTime) layout.addWidget(self.slrVolume) layout.addWidget(self.svgVolume) layout.addWidget(self.btnJmpCurrent) layout2.addWidget(self.cmbRepeat) layout2.addWidget(self.cmbShuffle) # TODO load here from previous session self.queuedSongs=[] self._onQueueUpdate() self.connect(self.slrVolume, QtCore.SIGNAL('valueChanged(int)'),self.onVolumeSliderChange) self.connect(self.slrTime, QtCore.SIGNAL('sliderReleased()'),self.onTimeSliderChange) self.connect(self.cmbRepeat, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbRepeatChanged) self.connect(self.cmbShuffle, QtCore.SIGNAL('currentIndexChanged(int)'),self.onCmbShuffleChanged) def addSongsToQueue(self, songs): self.queuedSongs.extend(songs) self._onQueueUpdate() if self.cmbShuffle.currentIndex()!=PC_RANDOM_QUEUE: self.cmbShuffle.setCurrentIndex(PC_RANDOM_QUEUE) def _onQueueUpdate(self): """This method gets called whenever the queue is updated""" self.cmbShuffle.setItemText(PC_RANDOM_QUEUE, "Queue (%i)"%(len(self.queuedSongs))) def onBtnJmpCurrentClick(self): plugins.getPlugin("Playlist").getPlaylist().ensureVisible(monty.getCurrentSong().getID()) def onStateChange(self, params): newState=monty.getStatus()['state'] map(lambda o: o.setEnabled(newState!='stop'), self.objects) if newState=='play': self.btnPlayPause.changeIcon('gfx/media-playback-pause.svg') self.btnPlayPause.setText('pauze') elif newState=='pause' or newState=='stop': self.btnPlayPause.changeIcon('gfx/media-playback-start.svg') self.btnPlayPause.setText('play') def onVolumeChange(self, params): self.slrVolume.setValue(params['newVolume']) def onDisconnect(self, params): map(lambda o: o.setEnabled(False), self.objects) def onTimeChange(self, params): if not self.slrTime.isSliderDown(): self.slrTime.setValue(params['newTime']) def onSongChange(self, params): try: self.slrTime.setMaximum(monty.getStatus()['length']) self.slrTime.setEnabled(True) except: pass # look in another thread for the songs in the current album! params=() start_new_thread(self.findAlbumSongs, params) def beforeSongChange(self, params): nextID=None song=monty.getCurrentSong() # decide here what next song to play! repeat=self.cmbRepeat.currentIndex() random=self.cmbShuffle.currentIndex() # is the current song the last of the album? try: eofAlbum=int(song.getTrack())==int(self.curAlbumSongs[-1].getTrack()) except: eofAlbum=False if repeat==PC_REPEAT_NO: # no repeat, nothing to see here! Pass on! pass elif repeat==PC_REPEAT_SONG: # we must repeat the previous song! nextID=params['curSongID'] elif repeat==PC_REPEAT_ALBUM: # check if we are at the last track, if it is, we must start a new! if eofAlbum: nextID=self.curAlbumSongs[0].getID() elif repeat==PC_REPEAT_PLAYLIST: # repeating the playlist is handled by monty itself; # it is set in onCmbRepeatChanged. pass if repeat!=PC_REPEAT_SONG: if random==PC_RANDOM_QUEUE: # we must check here for the queue, because, if the queue is empty, we must # choose the next one according the method! # pick the next song from the queue. Simple as that :) if len(self.queuedSongs): # pick the front nextID=self.queuedSongs.pop(0) self._onQueueUpdate() else: # no songs anymore, so we must restore the old mode! # We should never arrive here though ... self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle'))) if random==PC_RANDOM_NO: # just follow our leader Monty. pass elif random==PC_RANDOM_SONG: # pick a random song! This depends on what repeat-mode we're in. if repeat==PC_REPEAT_NO or repeat==PC_REPEAT_PLAYLIST: # we don't repeat anything, so we can just let monty pick the # next random one! pass elif repeat==PC_REPEAT_ALBUM and self.curAlbumSongs: # pick random song from current album nextID=self.curAlbumSongs[randint(0,len(self.curAlbumSongs)-1)].getID() elif random==PC_RANDOM_ALBUM: # pick a random album! This means, we pick the first song of a random # album. if eofAlbum and (repeat==PC_REPEAT_PLAYLIST or repeat==PC_REPEAT_NO): # all first songs of an album albums=filter(lambda s: s.getAlbum() and s.getTrack()==1, monty.listPlaylist()) nextID=albums[randint(0,len(albums)-1)].getID() else: # we're not at end of album, so we fetch the next id # We must do this, because albums are not necesseraly in the same order for i in xrange(len(self.curAlbumSongs)): if self.curAlbumSongs[i].getTrack()==song.getTrack(): nextID=self.curAlbumSongs[i+1].getID() break if random==PC_RANDOM_QUEUE: # now here reset the mode to the previous, if needed if len(self.queuedSongs)==0: self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('oldshuffle'))) if nextID!=None: monty.play(nextID) def getCurAlbumSongs(self): return self.curAlbumSongs def findAlbumSongs(self): """This method looks for the songs in the album of current playing song.""" song=monty.getCurrentSong() if self.curAlbumSongs and clSong.isSameAlbum(song, self.curAlbumSongs[0]): return self.curAlbumSongs=None if not song or not song.getAlbum(): return self.curAlbumSongs=filter(lambda s: clSong.isSameAlbum(s, song), monty.listPlaylist()) self.curAlbumSongs=sorted(self.curAlbumSongs, lambda l,r: numeric_compare(l.getTrack(), r.getTrack())) def onBtnPlayPauseClick(self): status=monty.getStatus() if status['state']=='play': monty.pause() self.p.extended("Toggling playback") elif status['state']=='stop': monty.play(None) self.p.extended("Pausing playback") else: monty.resume() def onBtnStopClick(self): monty.stop() self.p.extended("Stopping playback") def onBtnPreviousClick(self): monty.previous() self.p.extended("Playing previous") def onBtnNextClick(self): monty.next() self.p.extended("Playing next") def onTimeSliderChange(self): monty.seek(self.slrTime.value()) def onVolumeSliderChange(self): v=self.slrVolume.value() monty.setVolume(v) if v<=1: mode='mute' else: mode=('0', 'min', 'med', 'max')[int(3*v/100)] self.svgVolume.load('gfx/stock_volume-%s.svg'%(mode)) def onCmbRepeatChanged(self, newval): self.p.setSetting('repeat', newval) if newval==PC_REPEAT_PLAYLIST: monty.repeat(1) else: monty.repeat(0) def onCmbShuffleChanged(self, newval): if newval==PC_RANDOM_QUEUE: # must do some extra's if moving to queued-mode if len(self.queuedSongs): self.p.setSetting('oldshuffle', self.p.getSetting('shuffle')) else: self.cmbShuffle.setCurrentIndex(int(self.p.getSetting('shuffle'))) return else: # clear the queued songs when switching self.queuedSongs=[] self._onQueueUpdate() self.p.setSetting('shuffle', newval) if newval==PC_RANDOM_SONG: monty.random(1) else: monty.random(0) # save and load the queue def saveQueue(self): # save the ids as a list of space-separated numbers self.p.extended("saving queue") self.p.setSetting('queue', str(self.queuedSongs)[1:-1].replace(',', '')) def loadQueue(self): # just read all the numbers! self.p.extended("loading queue") self.queuedSongs=[] i=0 ids=self.p.getSetting('queue').split(' ') for id in ids: try: self.queuedSongs.append(int(id)) except: pass self._onQueueUpdate() class pluginPlayControl(Plugin): o=None def __init__(self, winMain): Plugin.__init__(self, winMain, 'PlayControl') self.addMontyListener('onStateChange', self.onStateChange) self.addMontyListener('beforeSongChange', self.beforeSongChange) self.addMontyListener('onSongChange', self.onSongChange) self.addMontyListener('onVolumeChange', self.onVolumeChange) self.addMontyListener('onReady', self.onStateChange) self.addMontyListener('onDisconnect', self.onDisconnect) self.addMontyListener('onTimeChange', self.onTimeChange) def _load(self): self.o=wgPlayControl(self, None) self.o.loadQueue() def _unload(self): self.o.saveQueue() self.o=None def getInfo(self): return "Have total control over the playing!" def addSongsToQueue(self, songs): return self.o.addSongsToQueue(songs) def onStateChange(self, params): self.o.onStateChange(params) def beforeSongChange(self, params): self.o.beforeSongChange(params) def onSongChange(self, params): self.o.onSongChange(params) def onVolumeChange(self, params): self.o.onVolumeChange(params) def onDisconnect(self, params): self.o.onDisconnect(params) def onTimeChange(self, params): self.o.onTimeChange(params) def _getDockWidget(self): return self._createDock(self.o)