class Stream: def __init__(self, url): self.player = MediaPlayer(url) self.stream_url = url self._volume = 0 self.player.audio_set_volume(self._volume) self.player.play() self.timer = None @property def volume(self): return self._volume @volume.setter def volume(self, val): assert int(val) == val self._volume = int(val) self.set_volume(int(val)) def grad_change_vol(self, d_vol, time): mod = d_vol - self.volume if not mod == 0: one_step_mod = mod/abs(mod) secs_per_step=time/abs(mod) for _ in range(abs(mod)): self.volume += one_step_mod sleep(secs_per_step) def set_volume(self, vol): self.player.audio_set_volume(vol) def set_sleep_timer(self, sleep_timer_class=My_Sleep_Timer): if self.timer != None: self.timer.terminate() self.timer = sleep_timer_class(self) self.timer.start() def quit(self): self.timer.terminate() self.player.stop()
class AudioFile(): """ Class for the audiofile. Provides all methods to interact with the audio: play, check answer, ... """ def __init__(self): self._filename = _read_file() self._audio = MediaPlayer(AUDIO_DIR + "/" + self.filename) self.play() def play(self): """play the audio of the file""" self._audio.stop() self._audio.play() logger.info('playing ' + self.filename) def check_answer(self, answer): """ Check if the provided answer is correct. The format should be sound + tone and no seperator between syllables ie shang4hai3 """ return answer == self._filename.split("__")[0].replace("_", "") def get_pinyin(self): return get_pinyin(self.filename) def get_id(self): return get_id(self.filename) def get_extension(self): return get_extension(self.extension) def __del__(self): self._audio.release() @property def filename(self): return self._filename
class Player: """The class provides basic media playback functions. The class provides functions to load local media and play it. """ def __init__(self): """Initializes empty class variables. The class initializes the following class variables: vlc_player The variable, which is used to play music via an instance of a vlc.MediaPlayer. It is initialized with 'None'. wish_list A list of file paths, that determine the files that are left to play. It is initialized as an empty list. playing_thread A thread that calls 'self.play()' and which is joined in the destructor. It is initialized and started. keep_playing A boolean that signals the method 'self.play()' and thus the thread 'self.playing_thread' when to stop the playback and prepare to be destructed. It is initialized with 'True'. is_paused The boolean signals the method 'play()' when the current playback is paused and is toggled by the method 'self.pause()'. It is initialized with 'False'.. Semaphores ---------- change_player_list A 'threading.Semaphore' that should be acquired and released when ever 'self.wish_list' or 'self.wish_list'. """ self.vlc_player = MediaPlayer() self.change_player_list = Semaphore() self.wish_list = list() self.keep_playing = True self.is_paused = False self.playing_thread = Thread(target=self.play) self.playing_thread.start() def __del__(self): """The destructor prepares the object to be destructed, by joining all threads and stopping the music. """ if isinstance(self.vlc_player, MediaPlayer): self.vlc_player.stop() # wait for the playing_thread to terminate self.keep_playing = False self.playing_thread.join() # delet created instance variables del self.vlc_player del self.keep_playing del self.is_paused del self.playing_thread del self.change_player_list def pause(self): """Pauses or resumes playing music. Calling this method toggles the 'pause' function of 'self.vlc_player' in case it is an instance of 'vlc.MediaPlayer'. Also the instance variable 'self.is_paused' toggled, such that the method 'self.play()' recognizes that the current title is paused. """ if isinstance(vlc_player, MediaPlayer): self.vlc_player.pause() if self.keep_playing: self.is_paused = False else: self.is_paused = True def play(self): """The method starts media playback and continues until 'self.keep_playing' is set to False. The method starts media playback for file paths mentioned in 'self.wish_list'. For the playback the instance variable 'self.vlc_player', which declared as a 'MediaPlayer' in case it is of any other type while there is at least one element in 'self.wish_list'. When all elements of 'self.wish_list' are played or removed, the method waits until new elements are added to 'self.wish_list'. The method only returns if the variable 'self.keep_playing' is set to False. In order to access and edit 'self.vlc_player' the 'Semaphore' 'self.change_player_list' is acquired and released when ever 'self.vlc_player' or 'self.wish_list' is edited. """ seconds_to_wait = 0.5 while self.keep_playing: if not self.is_paused: # self is expected to play until 'self.wish_list' is empty if len(self.wish_list) > 0: # there are currently some songs to play if isinstance(self.vlc_player, MediaPlayer): # the 'vls_player' is initialized with the expected type if self.vlc_player.is_playing(): # currently playing, nothing need to happen, # so it can sleep to spare some resources time.sleep(seconds_to_wait) else: # there is no song playing at the moment, but there are # tracks in 'self.wish_list' that still needs to be # played self.change_player_list.acquire() # load the media for the next song into vlc_player self.vlc_player.set_media(Media(self.wish_list[0])) self.vlc_player.play() # remove just loaded song from the wish_list self.wish_list.remove(self.wish_list[0]) self.change_player_list.release() # the length of 'self.wish_list' is not greater than 0, so there # is no song to play next. else: time.sleep(seconds_to_wait) else: # self is expected to pause, until the user requests to continue # the playback time.sleep(seconds_to_wait) def skip(self): """The method does not finish the current track but starts playing the next title in 'self.wish_list'. In case 'self.vlc_player' is an instance of 'MediaPlayer' and there is at least one element in 'self.wish_list' the method waits to get the rights to change 'self.vlc_player' by acquiring 'self.change_player_list'. After that, the method loads the first element of 'self.wish_list', loads that as new 'vlc.Media' for the 'self.vlc_player' and starts playing the new media. After that the 'self.change_player_list' is released. In order to access and edit 'self.vlc_player' the 'Semaphore' 'self.change_player_list' is acquired and released when ever 'self.vlc_player' or 'self.wish_list' is edited. """ if isinstance(self.vlc_player, MediaPlayer): if len(self.wish_list) > 0: self.change_player_list.acquire() # edit vlc_player self.vlc_player.set_media(Media(self.wish_list[0])) self.vlc_player.play() # edit wish_list self.wish_list.remove(self.wish_list[0]) self.change_player_list.release() def mute(self): """The method toggles the mute of the audio output of the media player 'self.vlc_player'. When 'self.vlc_player' is an instance of 'MediaPlayer', the mothed toggles the method 'self.vlc_player.audio_toggle_mute()'. """ if isinstance(self.vlc_player, MediaPlayer): self.vlc_player.audio_toggle_mute() def queue(self, file_path): """The method adds the string 'file_path' to the end of the list 'self.wish_list' and starts playing. If the given parameter 'file_path' is of the type 'str' and a valid file path, it is appended to the list 'wish_list'. In order to access and edit the 'self.wish_list' the 'Semaphore' 'self.change_player_list' is acquired and released when 'file_path' is added to 'self.wish_list'. Parameters: ----------- file_path A str of the file path towards a destination file that should be played by the player instance and which is added to 'self.wish_list'. """ if isinstance(file_path, str) and os.path.isfile(file_path): self.change_player_list.acquire() # add new media file to wish_list self.wish_list.append(file_path) self.change_player_list.release()
class SoundService: __instance = None # player = None def __init__(self): # self.player = None self.player = None self.vlc_instance = Instance self.playing_radio = False self.playing_alarm = False # pass def get_player(self): return self.player def set_player(self, player): self.player = player @staticmethod def get_instance(): if SoundService.__instance is None: SoundService.__instance = SoundService() return SoundService.__instance def play_default_alarm_sound(self): self.play_alarm_sound(DEFAULT_ALARM_FILEPATH) def play_alarm_sound(self, filepath): # here we use a MediaListPlayer instance as it offers the repeat functionality self.player = MediaListPlayer() self.player.set_playback_mode(PlaybackMode.loop) media_player = Media(filepath) if not FileService.file_exists(filepath): print( 'The alarm could not be played. Fallback to well known default alarm' ) media_player = Media(DEFAULT_ALARM_FILEPATH) media_list = MediaList() media_list.add_media(media_player) self.player.set_media_list(media_list) result = self.player.play() if result == -1: # TODO test this print('Something went wrong') self.playing_alarm = True def stop_alarm_sound(self): self.player.stop() self.playing_alarm = False def pause_alarm_sound(self): self.player.pause() self.playing_radio = False def play_webradio(self, url): self.player = MediaPlayer(url) self.player.play() self.playing_radio = True def pause_webradio(self): self.player.pause() self.playing_radio = False def stop_webradio(self): self.player.stop() self.playing_radio = False def pause_playing(self): print('Pausing alarm') if self.playing_radio: self.pause_webradio() else: self.pause_alarm_sound() def stop_playing(self): print('Stopping alarm') if self.playing_radio: self.stop_webradio() else: self.stop_alarm_sound()
class Song: """Represents a song in the library. """ ID3_COLUMNS = ("title", "artist", "album", "genre", "year") NON_ID3_COLUMNS = ("length", "date_modified") def __init__(self, file_path, title = None, artist = None, album = None, genre = None, year = None, override_id3 = True): """ Given an absolute file path, and data about the song a initialize a Song object. Parses ID3 tags for additional metadata if it exists. If the override_id3 is true, the given name and artist will override the name and artist contained in the ID3 tag. @param file_path: str @param title: str @param artist: str @param album: str @param genre: str @param year: int @param override_id3: bool """ self._file_path = file_path self._mp = None self._time = None # What time, in seconds, of the song playback to play at # Fill in column values, first by parsing ID3 tags and then manually self._columns = {} for tag_name, tag in zip(Song.ID3_COLUMNS, Song._get_ID3_tags(file_path)): self._columns[tag_name] = tag self._columns["length"] = int(MP3(file_path).info.length + 0.5) # Read length and round to nearest integer self._columns["date_modified"] = Song.get_date_modified(file_path) # If overriding, only do so for passed parameters if override_id3: self._columns["title"] = title if title is not None else self._columns["title"] self._columns["artist"] = artist if artist is not None else self._columns["artist"] self._columns["album"] = album if album is not None else self._columns["album"] self._columns["genre"] = genre if genre is not None else self._columns["genre"] self._columns["year"] = year if year is not None else self._columns["year"] def init(self): if self._mp is None: # Only initialize if not already initialized self._mp = MediaPlayer(self._file_path) def play(self, sleep_interval = 0.1): """ Plays this song. """ # Create the MediaPlayer on demand to save system resources (and prevent VLC from freaking out). if self._mp is None: raise SongException("Song not initialized") self._mp.play() if self._time is not None: self._mp.set_time(int(self._time * 1000)) # Seconds to milliseconds self._time = None # Sleep a bit to allow VLC to play the song, so self.playing() returns properly time.sleep(sleep_interval) def pause(self): """ Pauses this song, if it's playing. """ if self._mp is None: raise SongException("Song not initialized") self._mp.pause() def set_time(self, time): """ Sets the current play time of this song, so when the song is played (or if it's currently played) it will play from that time, or start playing from that time now if the song is currently playing. Given time should be in seconds. @param time: int or float """ if time < 0: raise SongException("Can't jump to negative timestamp") if time < self._columns["length"]: self._time = time if self.playing(): self.stop() self.init() self.play() def get_current_time(self): """ Returns the current play time, in seconds, of this song, if it's playing. @return float """ if self.playing(): return self._mp.get_time() / 1000 def set_volume(self, percentage): """ Sets the volume to the given percentage (between 0 and 100). @param percentage: int """ if self._mp is None: raise SongException("Song not initialized") elif percentage < 0 or percentage > 100: raise SongException("Percentage out of range") if self.playing(): self._mp.audio_set_volume(percentage) def get_volume(self): """ Returns the current volume of the song, if it's playing. @return int """ if self._mp is None: raise SongException("Song not initialized") if self.playing(): return self._mp.audio_get_volume() def mute(self): """ Mutes the song, if it's playing. """ if self._mp is None: raise SongException("Song not initialized") if self.playing(): self._mp.audio_set_mute(True) def unmute(self): """ Unmutes the song, if it's playing and is muted. """ if self._mp is None: raise SongException("Song not initialized") if self.playing(): self._mp.audio_set_mute(False) def playing(self): """ Returns if this song is playing or not (ie currently paused). @return: bool """ if self._mp is None: raise SongException("Song not initialized") return self._mp.is_playing() def reset(self): """ Resets the song to the beginning. """ if self._mp is None: raise SongException("Song not initialized") self._mp.stop() def stop(self): """ Terminates this song, freeing system resources and cleaning up. """ if self._mp is not None: self._mp.stop() self._mp = None def delete_from_disk(self): """ Deletes this song from the hard drive, returning if the deletion was successful. @return bool """ os.remove(self._file_path) def set_ID3_tag(tag, value): """ Sets this song's ID3 tag to the given value, returning if the set operation succeeded. @param tag: str @param value: str @return bool """ if tag not in ID3_COLUMNS: return False tags = EasyID3(self.file_path) tags[tag] = value tags.save() return True @staticmethod def _get_ID3_tags(file_path): """ Given a file path to an mp3 song, returns the ID3 tags for title, artist, album, genre, and year (in that order), or empty tuple if no tags are found. @param filename: str @return: tuple of ID3 tags """ ret = [None for _ in range(len(Song.ID3_COLUMNS))] try: tags = EasyID3(file_path) for tag in Song.ID3_COLUMNS: if tag in tags: ret.append(tags[tag][0]) else: ret.append(None) except ID3NoHeaderError: pass if not ret[Song.ID3_COLUMNS.index("title")]: ret[Song.ID3_COLUMNS.index("title")] = file_path.split("/")[-1][: -4] # Parse file name from absolute file path and delete file extension return tuple(ret) @staticmethod def get_date_modified(file_path): """ Gets the date modified of the file indicated by the given file path. @param file_path: str @return: datetime.datetime """ return datetime.fromtimestamp(os.path.getmtime(file_path)) @staticmethod def set_date_modified(file_path, date): """ Sets the "date modified" of a file. @param file_path: str @param date: datetime.datetime """ os.utime(file_path, (0, time.mktime(date.timetuple()))) def __str__(self): ret = self["title"] if self["artist"] is not None: ret += " - " + self["artist"] return ret def __eq__(self, other): if not isinstance(other, self.__class__): return False for col in self._columns: if col != "date_modified" and self[col] != other[col]: # Don't check if 'date modified' columns match return False return True # Getters, setters below def __getitem__(self, key): return self._columns[key] def __contains__(self, item): return item in self._columns