From 6a1320f8f95839b6fa168157cc41a296c29a3308 Mon Sep 17 00:00:00 2001 From: Terry Geng Date: Wed, 26 Feb 2020 22:09:53 +0800 Subject: [PATCH] feat: three playback mode "one-shot", "loop", "random" fix: bugs when you are on the last item and you want remove it. Some tips for testing: Observe the behavior when you are playing the last item and you remove the last item, for all three modes. --- command.py | 30 ++++++++++++++++++++++++++---- configuration.default.ini | 6 ++++++ interface.py | 30 +++++++++++++++++++++++------- media/playlist.py | 34 +++++++++++++++++++++++++++------- mumbleBot.py | 34 +++++++++++++++------------------- 5 files changed, 97 insertions(+), 37 deletions(-) diff --git a/command.py b/command.py index 8fe4015..b46d213 100644 --- a/command.py +++ b/command.py @@ -47,6 +47,7 @@ def register_all_commands(bot): bot.register_command(constants.commands('list_file'), cmd_list_file) bot.register_command(constants.commands('queue'), cmd_queue) bot.register_command(constants.commands('random'), cmd_random) + bot.register_command(constants.commands('mode'), cmd_mode) bot.register_command(constants.commands('drop_database'), cmd_drop_database) def send_multi_lines(bot, lines, text): @@ -533,9 +534,17 @@ def cmd_remove(bot, user, text, command, parameter): removed = None if index == var.playlist.current_index: removed = var.playlist.remove(index) - if bot.is_playing and not bot.is_pause: - bot.stop() - bot.launch_music(index) + + if index < len(var.playlist): + if not bot.is_pause: + bot.kill_ffmpeg() + var.playlist.current_index -= 1 + # then the bot will move to next item + + else: # if item deleted is the last item of the queue + var.playlist.current_index -= 1 + if not bot.is_pause: + bot.kill_ffmpeg() else: removed = var.playlist.remove(index) @@ -593,12 +602,25 @@ def cmd_queue(bot, user, text, command, parameter): send_multi_lines(bot, msgs, text) - def cmd_random(bot, user, text, command, parameter): bot.stop() var.playlist.randomize() bot.launch_music(0) +def cmd_mode(bot, user, text, command, parameter): + if not parameter: + bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text) + return + if not parameter in ["one-shot", "loop", "random"]: + bot.send_msg(constants.strings('unknown_mode', mode=parameter), text) + else: + var.playlist.set_mode(parameter) + if parameter == "random": + bot.stop() + var.playlist.randomize() + bot.launch_music(0) + + def cmd_drop_database(bot, user, text, command, parameter): var.db.drop_table() var.db = Database(var.dbfile) diff --git a/configuration.default.ini b/configuration.default.ini index 7047275..e71393d 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -38,6 +38,7 @@ admin = User1;User2; music_folder = music_folder/ # Folder that stores the downloaded music. tmp_folder = /tmp/ +database_path = database.db pip3_path = venv/bin/pip auto_check_update = True logfile = @@ -131,6 +132,7 @@ joinme = joinme queue = queue repeat = repeat random = random +mode = mode update = update list_file = listfile @@ -183,6 +185,8 @@ database_dropped = Database dropped. All records have gone. new_version_found =

Update Available!

New version of botamusique is available, send !update to update! start_updating = Start updating... file_missed = Music file '{file}' missed! This item has been removed from the playlist. +unknown_mode = Unknown playback mode '{mode}'. It should be one of one-shot, loop, random. +current_mode = Current playback mode is {mode}. help =

Commands

Control @@ -192,6 +196,8 @@ help =

Commands

  • !stop - stop playing
  • !skip - jump to the next song
  • !volume {volume} - get or change the volume (from 0 to 100)
  • +
  • !mode [{mode}] - get or set the playback mode, {mode} should be one of one-shot (play the playlist + once), loop (looping through the playlist), random (randomize the playlist)
  • !duck on/off - enable or disable ducking function
  • !duckv - set the volume of the bot when ducking is activated
  • !duckthres - set the threshold of volume to activate ducking (3000 by default)
  • diff --git a/interface.py b/interface.py index e92aa1e..d9fb77c 100644 --- a/interface.py +++ b/interface.py @@ -220,13 +220,23 @@ def post(): logging.info("web: delete from playlist: " + util.format_debug_song_string(music)) if var.playlist.length() >= int(request.form['delete_music']): - if int(request.form['delete_music']) == var.playlist.current_index: - var.playlist.remove(int(request.form['delete_music'])) - if var.botamusique.is_playing and not var.botamusique.is_pause: - var.botamusique.stop() - var.botamusique.launch_music(int(request.form['delete_music'])) + index = int(request.form['delete_music']) + + if index == var.playlist.current_index: + var.playlist.remove(index) + + if index < len(var.playlist): + if not var.botamusique.is_pause: + var.botamusique.kill_ffmpeg() + var.playlist.current_index -= 1 + # then the bot will move to next item + + else: # if item deleted is the last item of the queue + var.playlist.current_index -= 1 + if not var.botamusique.is_pause: + var.botamusique.kill_ffmpeg() else: - var.playlist.remove(int(request.form['delete_music'])) + var.playlist.remove(index) elif 'play_music' in request.form: @@ -254,8 +264,12 @@ def post(): action = request.form['action'] if action == "randomize": var.botamusique.stop() - var.playlist.randomize() + var.playlist.set_mode("random") var.botamusique.resume() + if action == "one-shot": + var.playlist.set_mode("one-shot") + if action == "loop": + var.playlist.set_mode("loop") elif action == "stop": var.botamusique.stop() elif action == "pause": @@ -269,12 +283,14 @@ def post(): var.botamusique.volume_set = var.botamusique.volume_set + 0.03 else: var.botamusique.volume_set = 1.0 + var.db.set('bot', 'volume', str(var.botamusique.volume_set)) logging.info("web: volume up to %d" % (var.botamusique.volume_set * 100)) elif action == "volume_down": if var.botamusique.volume_set - 0.03 > 0: var.botamusique.volume_set = var.botamusique.volume_set - 0.03 else: var.botamusique.volume_set = 0 + var.db.set('bot', 'volume', str(var.botamusique.volume_set)) logging.info("web: volume up to %d" % (var.botamusique.volume_set * 100)) if(var.playlist.length() > 0): diff --git a/media/playlist.py b/media/playlist.py index fbb833a..7e569f7 100644 --- a/media/playlist.py +++ b/media/playlist.py @@ -6,12 +6,20 @@ import json import logging class PlayList(list): - current_index = 0 + current_index = -1 version = 0 # increase by one after each change + mode = "one-shot" # "loop", "random" def __init__(self, *args): super().__init__(*args) + def set_mode(self, mode): + # modes are "one-shot", "loop", "random" + self.mode = mode + var.db.set('playlist', 'mode', mode) + if mode == "random": + self.randomize() + def append(self, item): self.version += 1 item = util.get_music_tag_info(item) @@ -45,15 +53,27 @@ class PlayList(list): return items def next(self): - self.version += 1 if len(self) == 0: return False + self.version += 1 logging.debug("playlist: Next into the queue") - self.current_index = self.next_index() - - return self[self.current_index] + if self.current_index < len(self) - 1: + self.current_index += 1 + return self[self.current_index] + else: + self.current_index = 0 + if self.mode == "one-shot": + self.clear() + return False + elif self.mode == "loop": + return self[0] + elif self.mode == "random": + self.randomize() + return self[0] + else: + raise TypeError("Unknown playlist mode '%s'." % self.mode) def update(self, item, index=-1): self.version += 1 @@ -116,8 +136,8 @@ class PlayList(list): def clear(self): self.version += 1 - self.current_index = 0 - self.clear() + self.current_index = -1 + super().clear() def save(self): var.db.remove_section("playlist_item") diff --git a/mumbleBot.py b/mumbleBot.py index 2cafa87..b0210a1 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -90,8 +90,6 @@ class MumbleBot: root.setLevel(logging.ERROR) logging.error("Starting in ERROR loglevel") - var.playlist = PlayList() - var.user = args.user var.music_folder = var.config.get('bot', 'music_folder') var.is_proxified = var.config.getboolean( @@ -100,7 +98,6 @@ class MumbleBot: self.nb_exit = 0 self.thread = None self.thread_stderr = None - self.is_playing = False self.is_pause = False self.playhead = -1 self.song_start_at = -1 @@ -378,7 +375,6 @@ class MumbleBot: util.pipe_no_wait(pipe_rd) # Let the pipe work in non-blocking mode self.thread_stderr = os.fdopen(pipe_rd) self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=480) - self.is_playing = True self.is_pause = False self.song_start_at = -1 self.playhead = 0 @@ -560,11 +556,7 @@ class MumbleBot: if self.thread is None or not raw_music: # Not music into the buffet - if self.is_playing: - # get next music - self.is_playing = False - if not self.is_pause and len(var.playlist) > 0: - var.playlist.next() + if not self.is_pause and var.playlist.next(): self.launch_music() self.async_download_next() @@ -610,35 +602,38 @@ class MumbleBot: self.thread.kill() self.thread = None var.playlist.clear() - self.is_playing = False logging.info("bot: music stopped. playlist trashed.") def stop(self): + # stop and move to the next item in the playlist + self.is_pause = True + self.kill_ffmpeg() + self.playhead = 0 + var.playlist.next() + logging.info("bot: music stopped.") + + def kill_ffmpeg(self): # Kill the ffmpeg thread if self.thread: self.thread.kill() self.thread = None - self.is_playing = False - self.is_pause = True self.song_start_at = -1 - self.playhead = 0 - var.playlist.next() - logging.info("bot: music stopped.") def pause(self): # Kill the ffmpeg thread if self.thread: self.thread.kill() self.thread = None - self.is_playing = False self.is_pause = True self.song_start_at = -1 logging.info("bot: music paused at %.2f seconds." % self.playhead) def resume(self): - self.is_playing = True self.is_pause = False + if var.playlist.current_index == -1: + var.playlist.next() + music = var.playlist.current_item() if music['type'] == 'radio' or self.playhead == 0 or not self.check_item_path_or_remove(): @@ -700,7 +695,7 @@ if __name__ == '__main__': parser.add_argument("--config", dest='config', type=str, default='configuration.ini', help='Load configuration from this file. Default: configuration.ini') parser.add_argument("--db", dest='db', type=str, - default='database.db', help='database file. Default: database.db') + default=None, help='database file. Default: database.db') parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Only Error logs") @@ -725,9 +720,9 @@ if __name__ == '__main__': args = parser.parse_args() - var.dbfile = args.db config = configparser.ConfigParser(interpolation=None, allow_no_value=True) parsed_configs = config.read(['configuration.default.ini', args.config], encoding='utf-8') + var.dbfile = args.db if args.db is not None else config.get("bot", "database_path", fallback="database.db") if len(parsed_configs) == 0: logging.error('Could not read configuration from file \"{}\"'.format( @@ -753,6 +748,7 @@ if __name__ == '__main__': handler.setFormatter(formatter) root.addHandler(handler) + var.playlist = PlayList() # playlist should be initialized after the database var.botamusique = MumbleBot(args) command.register_all_commands(var.botamusique)