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.
This commit is contained in:
		
							
								
								
									
										30
									
								
								command.py
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = <h3>Update Available!</h3> New version of botamusique is available, send <i>!update</i> 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 <i>one-shot</i>, <i>loop</i>, <i>random</i>.
 | 
			
		||||
current_mode = Current playback mode is <i>{mode}</i>.
 | 
			
		||||
 | 
			
		||||
help = <h3>Commands</h3>
 | 
			
		||||
       <b>Control</b>
 | 
			
		||||
@@ -192,6 +196,8 @@ help = <h3>Commands</h3>
 | 
			
		||||
       <li> <b>!<u>st</u>op </b> - stop playing </li>
 | 
			
		||||
       <li> <b>!<u>sk</u>ip </b> - jump to the next song </li>
 | 
			
		||||
       <li> <b>!<u>v</u>olume </b> {volume} - get or change the volume (from 0 to 100) </li>
 | 
			
		||||
       <li> <b>!<u>m</u>ode </b> [{mode}] - get or set the playback mode, {mode} should be one of <i>one-shot</i> (play the playlist
 | 
			
		||||
       once), <i>loop</i> (looping through the playlist), <i>random</i> (randomize the playlist)</li>
 | 
			
		||||
       <li> <b>!duck </b> on/off - enable or disable ducking function </li>
 | 
			
		||||
       <li> <b>!duckv </b> - set the volume of the bot when ducking is activated </li>
 | 
			
		||||
       <li> <b>!<u>duckt</u>hres </b> - set the threshold of volume to activate ducking (3000 by default) </li>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								interface.py
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								mumbleBot.py
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user