class PlayAudioMenu(QWidget): """ This class creates a custom widget with the Play Audio Menu elements and layout. """ __metaclass__ = ABCMeta def __init__(self, controller, parent=None): """ Constructor of the PlayAudioMenu class. :param controller: GUIController object. """ super(PlayAudioMenu, self).__init__(parent) self.controller = controller self.db = RAM_DB() backButton = Button_Back_PAM(self.controller).createButton(269, 100) self.playButton = Button_Play_PAM(self.controller).createButton(60, 60) self.pauseButton = Button_Pause_PAM(self.controller).createButton( 60, 60) nextButton = Button_Next_PAM(self.controller).createButton(60, 60) previousButton = Button_Previous_PAM(self.controller).createButton( 60, 60) (self.fileName, self.pathFiles, self.metaDataList) = self.db.getAudioDB() path = self.pathFiles[self.db.getSelection()] audioController = AudioController() self.audioObject = audioController.getAudioObject() self.textLabel = CustomLabel().createLabel(path, Qt.AlignCenter) self.actualTimeLabel = CustomLabel().createLabel( "00:00", Qt.AlignCenter) self.totalTimeLabel = CustomLabel().createLabel( "00:00", Qt.AlignCenter) self.timeSlider = TimeSlider(0, 100, 0, self.sliderValueChangedByUser) verticalBoxLayout = QVBoxLayout() hRepTimeBox = QHBoxLayout() hRepButtonsBox = QHBoxLayout() hImageBox = QHBoxLayout() hButtonsMenuBox = QHBoxLayout() verticalBoxLayout.setContentsMargins(0, 0, 0, 0) verticalBoxLayout.addStretch() verticalBoxLayout.addStretch() verticalBoxLayout.addWidget(self.textLabel) verticalBoxLayout.addStretch() hImageBox.addStretch() self.imgQLabel = QLabel() self.imgQLabel.setMaximumHeight(150) self.imgQLabel.setMaximumWidth(150) self.pixmapCover = QPixmap() loaded = self.pixmapCover.load( getArtworkPath(self.metaDataList[self.db.getSelection()])) if (loaded == False): self.pixmapCover.load(self.db.getArtworkNotFoundPath()) self.imgQLabel.setPixmap(self.pixmapCover.scaled(150, 150)) hImageBox.addWidget(self.imgQLabel) hImageBox.addStretch() verticalBoxLayout.addLayout(hImageBox) verticalBoxLayout.addStretch() hRepTimeBox.addWidget(self.actualTimeLabel) hRepTimeBox.addStretch() hRepTimeBox.addWidget(self.totalTimeLabel) verticalBoxLayout.addLayout(hRepTimeBox) verticalBoxLayout.addWidget(self.timeSlider) verticalBoxLayout.addStretch() hRepButtonsBox.addStretch() if (self.audioObject.getStatus() == AudioStatus.PAUSED): self.pauseButton.hide() else: self.playButton.hide() hRepButtonsBox.addWidget(previousButton) hRepButtonsBox.addStretch() hRepButtonsBox.addWidget(self.playButton) hRepButtonsBox.addWidget(self.pauseButton) hRepButtonsBox.addStretch() hRepButtonsBox.addWidget(nextButton) hRepButtonsBox.addStretch() verticalBoxLayout.addLayout(hRepButtonsBox) verticalBoxLayout.addStretch() hButtonsMenuBox.addWidget(backButton) hButtonsMenuBox.addStretch() verticalBoxLayout.addLayout(hButtonsMenuBox) self.setLayout(verticalBoxLayout) def sliderValueChangedByUser(self): """ Method when the slider notifies a change in his value made by the user. """ self.audioObject.changeAudioSecond(self.timeSlider.getValue()) @abstractmethod def update(self, *args, **kwargs): """ Update method of the observer pattern. :param args: args :param kwargs: kwargs """ self.updateView(*args, **kwargs) def updateView(self, *args, arg1, arg2): """ Update view method of the observer pattern. :param args: Name of the notification. :param arg1: Other data. :param arg2: Other data. """ if (args[0] == "NewMetaData"): minutes = round((arg2[16] // 1000.0) // 60.0) seconds = round((arg2[16] // 1000.0) % 60.0) if minutes < 10: strMinutes = "0" + str(minutes) else: strMinutes = str(minutes) if seconds < 10: strSeconds = "0" + str(seconds) else: strSeconds = str(seconds) if arg2[1] == None: titleText = "Artista desconocido" + " - " + arg2[0] else: titleText = arg2[1] + " - " + arg2[0] self.textLabel.setText(titleText) self.totalTimeLabel.setText(strMinutes + ":" + strSeconds) self.timeSlider.setMaximum(arg2[16]) loaded = self.pixmapCover.load( getArtworkPath(self.metaDataList[self.db.getSelection()])) if (loaded == False): self.pixmapCover.load(self.db.getArtworkNotFoundPath()) self.imgQLabel.setPixmap(self.pixmapCover.scaled(150, 150)) if (self.audioObject.getStatus() == AudioStatus.PAUSED): self.pauseButton.hide() self.playButton.show() else: self.playButton.hide() self.pauseButton.show() elif (args[0] == "AudioPaused"): self.playButton.show() self.pauseButton.hide() elif (args[0] == "AudioResumed"): self.playButton.hide() self.pauseButton.show() elif (args[0] == "UpdateReproductionSecond"): minutes = round((arg1 // 1000) // 60) seconds = round((arg1 // 1000)) % 60 if minutes < 10: strMinutes = "0" + str(minutes) else: strMinutes = str(minutes) if seconds < 10: strSeconds = "0" + str(seconds) else: strSeconds = str(seconds) self.actualTimeLabel.setText(strMinutes + ":" + strSeconds) self.timeSlider.setValue(arg1)
class __AudioController: def __init__(self): """ Constructor of the AudioController class. """ self.db = RAM_DB() (self.fileName, self.pathFiles, self.metaDataList) = self.db.getAudioDB() self.path = None self.audioObject = AudioFile(self.notifyController) self.currentFMStation = None self.SI4703 = SI4703() self.playingRadio = False self.observers = [] self.GUICoolDown = False self.threadController = ThreadController() ############################################################################### #OBSERVER PATTERN ############################################################################### def register(self, observer): """ Register method of the observer pattern. :param observer: Object to subscribe. """ if not observer in self.observers: self.observers.append(observer) def unregister(self, observer): """ Untegister method of the observer pattern. :param observer: Object to unsubscribe. """ if observer in self.observers: self.observers.remove(observer) def unregister_all(self): """ Unsubscribe all the subscribers. """ if self.observers: del self.observers[:] def update_observers(self, *args, **kwargs): """ Notify to all the observers/subscribers. :param args: args :param kwargs: kwargs """ for observer in self.observers: observer.update(*args, **kwargs) ############################################################################### def getAudioObject(self): """ Returns the audio object, instance of the AudioFile class. :return: Current Audio File instance. """ return self.audioObject def loadAudio(self): """ Loads an audio file. (Plays it). """ if (self.playingRadio == True): self.stopRadio() if (self.audioObject.getStatus() == AudioStatus.NOFILE): self.audioObject.playAudio() else: self.audioObject.stopAudio() self.audioObject.playAudio() self.update_observers( "NewMetaData", arg1=self.path, arg2=self.metaDataList[self.db.getSelection()]) def startUpdateStatusThread(self): """ Starts a thread that updates the reproduction status by polling to the vlc lib. """ self.audioObject.startUpdateStatusThread() self.update_observers( "NewMetaData", arg1=self.path, arg2=self.metaDataList[self.db.getSelection()]) def nextTrack(self): """ Switch to the next track of the list. """ self.audioObject.stopAudio() if (self.db.getSelection() + 1 < len(self.pathFiles)): self.db.setSelection(self.db.getSelection() + 1) else: self.db.setSelection(0) self.loadAudio() def previousTrack(self): """ Switch to the previous track of the list. """ self.audioObject.stopAudio() if (self.db.getSelection() - 1 >= 0): self.db.setSelection(self.db.getSelection() - 1) else: self.db.setSelection(len(self.pathFiles) - 1) self.loadAudio() def nextTrackEvent(self): """ Manages the automatic change to the next track notifying to the GUI. """ if (self.db.getSelection() + 1 < len(self.pathFiles)): self.db.setSelection(self.db.getSelection() + 1) else: self.db.setSelection(0) self.path = self.pathFiles[self.db.getSelection()] self.update_observers( "NewMetaData", arg1=self.path, arg2=self.metaDataList[self.db.getSelection()]) def pause(self): """ Pauses the audio. """ self.audioObject.pauseAudio() self.update_observers("AudioPaused", arg1=None, arg2=None) def resume(self): """ Resumes a paused audio. """ self.audioObject.resumeAudio() self.update_observers("AudioResumed", arg1=None, arg2=None) def changeAudioSecond(self, second): """ Changes the current reproduction second to another second. :param second: New current second. """ self.audioObject.changeAudioSecond(second) def updateReproductionSecondEvent(self, second): """ Send an event to the observers when the current reproduction second changes. """ self.update_observers("UpdateReproductionSecond", arg1=second, arg2=None) def initRadio(self): """ Initialize the radio module if it's stopped. """ if self.playingRadio == False: #STOP the reproduction of Audio if self.audioObject.getStatus() != AudioStatus.NOFILE: self.audioObject.stopAudio() #Init the radio if (self.currentFMStation == None): self.currentFMStation = 92.3 self.update_observers("UpdateCurrentFMFrequency", arg1=self.currentFMStation, arg2=None) self.startGUICoolDown(3) self.SI4703.initRadio() self.SI4703.setVolume(15) self.SI4703.setChannel(self.currentFMStation) self.playingRadio = True def stopRadio(self): """ Stops the radio module if it's working. """ if self.playingRadio == True: self.SI4703.stopRadio() self.playingRadio = False def setCurrentFMFrequency(self, frequency): """ Changes the tuned frequency of the radio module. :param frequency: New frequency to tune. """ if (frequency >= 87.5 and frequency <= 108): self.currentFMStation = frequency self.update_observers("UpdateCurrentFMFrequency", arg1=self.currentFMStation, arg2=None) self.SI4703.setChannel(self.currentFMStation) def getCurrentFMFrequency(self): """ Returns the current tuned frequency. :return currentFMStation: Current tuned frequency. """ return self.currentFMStation def getCurrentFMStationName(self): """ Returns the name of the FM station (actually the frequency as string) :return currentFMStation: Current tuned frequency as string. """ return str(self.currentFMStation) def updateRadioObservers(self): """ Update all the observers related to the radio module. """ self.update_observers("UpdateCurrentFMFrequency", arg1=self.currentFMStation, arg2=None) self.update_observers("UpdateRadioChannelData", arg1=None, arg2=None) def nextFrequency(self): """ Changes to the next frequency. """ if (self.currentFMStation >= 87.5 and self.currentFMStation < 108): self.currentFMStation = round(self.currentFMStation + 0.1, 2) self.update_observers("UpdateCurrentFMFrequency", arg1=self.currentFMStation, arg2=None) def previousFrequency(self): """ Changes to the previous frequency. """ if (self.currentFMStation > 87.5 and self.currentFMStation <= 108): self.currentFMStation = round(self.currentFMStation - 0.1, 2) self.update_observers("UpdateCurrentFMFrequency", arg1=self.currentFMStation, arg2=None) def startChangeFrequencyThread(self): """ Starts the thread launched when we change between frequencies manually. """ if (self.threadController.getChangeFrequencyThread() != None): self.threadController.getChangeFrequencyThread().stop() self.threadController.setChangeFrequencyThread(None) self.changeFrequencyThread = ChangeFrequencyThread( 0.5, self.currentFMStation, self.setCurrentFMFrequency, self.startGUICoolDown) self.threadController.setChangeFrequencyThread( self.changeFrequencyThread) self.threadController.getChangeFrequencyThread().start() def seekUp(self): """ Seek up a new FM station. """ self.SI4703.seekUp(self.notifyController) def seekDown(self): """ Seek down a new FM station. """ self.SI4703.seekDown(self.notifyController) def setGUICoolDown(self, state): """ Sets the status of the cooldown of the GUI. :param state: State of the cooldown. """ if (state == False): self.update_observers("CoolDownEnded", arg1=None, arg2=None) self.GUICoolDown = state def getGUICoolDown(self): """ Returns the state of the cooldown of the GUI. :return GUICoolDown: Status of the cooldown of the GUI. """ return self.GUICoolDown def startGUICoolDown(self, ms=0.5): """ Starts the cooldown of the GUI when the radio module is busy. :param ms: Duration of the cooldown. """ self.update_observers("CoolDownStarted", arg1=None, arg2=None) buttonCoolDownThread = ButtonCoolDownThread( ms, self.setGUICoolDown) buttonCoolDownThread.start() def getPlayingRadio(self): """ Returns if the radio is being played or not. :return playingRadio: Status of the radio. """ return self.playingRadio def getStatus(self): """ Returns the current status of the reproduction. See AudioStatus class. :return: Current status. """ return self.audioObject.getStatus() def notifyController(self, notify, var=0): """ Method that other classes uses to notifie to the AudioController of the changes. :param notify: Kind of notification. :param var: Attached var. """ if (notify == "nextTrack"): self.nextTrackEvent() elif (notify == "updateReproductionSecond"): self.updateReproductionSecondEvent(var) elif (notify == "endOfList"): self.nextTrack() elif (notify == "seekFrequencyChanged"): self.currentFMStation = var self.update_observers("UpdateCurrentFMFrequency", arg1=self.currentFMStation, arg2=None) def __str__(self): return repr(self) + self.val
class AudioFileVLC: """ Plays MP3/WAV or other supported files using the VLC lib. """ def __init__(self, notifyAudioController): """ Constructor of the AudioFileVLC class. :param notifyAudioController: Method to notify of the changes to the Audio Controller. """ self.vlcInstance = vlc.Instance() self.mediaPlayer = self.vlcInstance.media_player_new() self.mediaList = self.vlcInstance.media_list_new() self.listMediaPlayer = self.vlcInstance.media_list_player_new() self.listMediaPlayer.set_media_player(self.mediaPlayer) self.db = RAM_DB() self.threadController = ThreadController() (self.fileName, self.pathFiles, self.metaDataList) = self.db.getAudioDB() self.notifyAudioController = notifyAudioController #This boolean avoid to notify of unnecesary changes to the AudioController, works as a flag self.avoidNotify = False #This boolean avoid to stop the medialistplayer when is already stopped because have reached the end of the list. self.reproductionEnded = False for i in range(0, len(self.pathFiles)): self.mediaList.insert_media(self.vlcInstance.media_new(self.pathFiles[i]), i) self.listMediaPlayer.set_media_list(self.mediaList) #For the VLC Event handler self.vlc_eventsMediaList = self.listMediaPlayer.event_manager() self.vlc_eventsMediaPlayer = self.mediaPlayer.event_manager() self.vlc_eventsMediaPlayer.event_attach(vlc.EventType.MediaPlayerEndReached, self.endOfSong, 1) self.vlc_eventsMediaList.event_attach(vlc.EventType.MediaListPlayerNextItemSet, self.nextItem, 1) self.reproductionStatusThread = None def playAudio(self): """ Plays an audio file. """ self.avoidNotify = True self.listMediaPlayer.play_item_at_index(self.db.getSelection()) # We only starts the thread that make polling for gets the current reproduction second if the current menu is the PlayAudioMenu if(self.db.getCurrentMenu() == "PlayAudioMenu"): self.startUpdateStatusThread() def startUpdateStatusThread(self): """ Starts a thread that updates the reproduction status by polling to the vlc lib. """ self.reproductionStatusThread = ReproductionStatusThread(self.mediaPlayer, self.notifyAudioController) self.threadController.setReproductionStatusThread(self.reproductionStatusThread) self.threadController.getReproductionStatusThread().start() def pauseAudio(self): """ Pauses the audio. """ self.listMediaPlayer.pause() def changeAudioSecond(self, second): """ Changes the current reproduction second to another second. :param second: New current second. """ self.mediaPlayer.set_time(second) def resumeAudio(self): """ Resumes a paused audio. """ self.listMediaPlayer.play() def stopAudio(self): """ Stops the reproduction of an audio. """ if (self.threadController.getReproductionStatusThread() != None): self.threadController.getReproductionStatusThread().stop() # At the end of the list, VLC Lib makes a stop() to the media player list by default. # If we make a stop() two times, it hangs if (self.reproductionEnded == False): self.listMediaPlayer.stop() else: self.reproductionEnded = False def getPath(self): """ Returns the path of the file that it's being reproduced. :return: String with the path. """ return self.path def nextItem(self, *args, **kwds): """ Method called by the VLC event manager when the reproduction item has changed. :param args: args :param kwds: kwds """ if (self.avoidNotify == False): self.notifyAudioController("nextTrack") else: self.avoidNotify = False def endOfSong(self, *args, **kwds): """ Method called by the VLC event manager when the reproduction item ends. :param args: args :param kwds: kwds """ # If the song ended & is the last of the list, must notify to the controller if self.db.getSelection() == (len(self.pathFiles)-1): self.reproductionEnded = True self.notifyAudioController("endOfList")
class __AudioFile: def __init__(self, notifyAudioController): """ Constructor of the AudioFile class. :param notifyAudioController: Method to notify of the changes to the Audio Controller. """ self.savedSecond = 0 self.status = AudioStatus.NOFILE self.audioFileObject = None self.db = RAM_DB() self.notifyAudioController = notifyAudioController (self.fileName, self.pathFiles, self.metaDataList) = self.db.getAudioDB() def playAudio(self): """ Plays the selected audio file, don't matter what kind of format. """ if (self.status == AudioStatus.NOFILE): self.audioFileObject = self.__selectAudioType( self.pathFiles[self.db.getSelection()]) self.audioFileObject.playAudio() self.status = AudioStatus.PLAYING elif (self.status == AudioStatus.PLAYING or self.status == AudioStatus.PAUSED): self.audioFileObject.stopAudio() self.audioFileObject = self.__selectAudioType( self.pathFiles[self.db.getSelection()]) self.audioFileObject.playAudio() def pauseAudio(self): """ Pauses the current audio, managing it with the appropriate lib. """ self.audioFileObject.pauseAudio() self.status = AudioStatus.PAUSED """ def resumeAudio(self, savedSecond): Resumes the current audio, managing it with the appropriate lib. :param savedSecond: Second from where it resumes the reproduction. self.audioFileObject.resumeAudio(savedSecond) self.status = AudioStatus.PLAYING """ def resumeAudio(self): """ Resumes the current audio, managing it with the appropriate lib. """ self.audioFileObject.resumeAudio() self.status = AudioStatus.PLAYING def changeAudioSecond(self, second): """ Changes the current reproduction second to another second. :param second: New current second. """ self.audioFileObject.changeAudioSecond(second) def stopAudio(self): """ Stops the reproduction of the current audio, managing it with the appropriate lib. """ self.status = AudioStatus.NOFILE self.audioFileObject.stopAudio() def startUpdateStatusThread(self): """ Starts a thread that updates the reproduction status by polling to the vlc lib. """ self.audioFileObject.startUpdateStatusThread() def __selectAudioType(self, path): """ Private method that calls to the appropriate object depending on what kind of audio we want to reproduce. (Polimorfism) :param path: Path to the audio file. :return: Audio object using the appropriate lib. """ if (path.endswith(".mp3") or path.endswith(".wav")): audioType = AudioFileVLC(self.notifyAudioController) return audioType def getStatus(self): """ Returns the current status of the reproduction. See AudioStatus class. :return: Current status. """ return self.status def __str__(self): return repr(self) + self.val