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:
parent
388016a5af
commit
6a1320f8f9
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('list_file'), cmd_list_file)
|
||||||
bot.register_command(constants.commands('queue'), cmd_queue)
|
bot.register_command(constants.commands('queue'), cmd_queue)
|
||||||
bot.register_command(constants.commands('random'), cmd_random)
|
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)
|
bot.register_command(constants.commands('drop_database'), cmd_drop_database)
|
||||||
|
|
||||||
def send_multi_lines(bot, lines, text):
|
def send_multi_lines(bot, lines, text):
|
||||||
@ -533,9 +534,17 @@ def cmd_remove(bot, user, text, command, parameter):
|
|||||||
removed = None
|
removed = None
|
||||||
if index == var.playlist.current_index:
|
if index == var.playlist.current_index:
|
||||||
removed = var.playlist.remove(index)
|
removed = var.playlist.remove(index)
|
||||||
if bot.is_playing and not bot.is_pause:
|
|
||||||
bot.stop()
|
if index < len(var.playlist):
|
||||||
bot.launch_music(index)
|
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:
|
else:
|
||||||
removed = var.playlist.remove(index)
|
removed = var.playlist.remove(index)
|
||||||
|
|
||||||
@ -593,12 +602,25 @@ def cmd_queue(bot, user, text, command, parameter):
|
|||||||
|
|
||||||
send_multi_lines(bot, msgs, text)
|
send_multi_lines(bot, msgs, text)
|
||||||
|
|
||||||
|
|
||||||
def cmd_random(bot, user, text, command, parameter):
|
def cmd_random(bot, user, text, command, parameter):
|
||||||
bot.stop()
|
bot.stop()
|
||||||
var.playlist.randomize()
|
var.playlist.randomize()
|
||||||
bot.launch_music(0)
|
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):
|
def cmd_drop_database(bot, user, text, command, parameter):
|
||||||
var.db.drop_table()
|
var.db.drop_table()
|
||||||
var.db = Database(var.dbfile)
|
var.db = Database(var.dbfile)
|
||||||
|
@ -38,6 +38,7 @@ admin = User1;User2;
|
|||||||
music_folder = music_folder/
|
music_folder = music_folder/
|
||||||
# Folder that stores the downloaded music.
|
# Folder that stores the downloaded music.
|
||||||
tmp_folder = /tmp/
|
tmp_folder = /tmp/
|
||||||
|
database_path = database.db
|
||||||
pip3_path = venv/bin/pip
|
pip3_path = venv/bin/pip
|
||||||
auto_check_update = True
|
auto_check_update = True
|
||||||
logfile =
|
logfile =
|
||||||
@ -131,6 +132,7 @@ joinme = joinme
|
|||||||
queue = queue
|
queue = queue
|
||||||
repeat = repeat
|
repeat = repeat
|
||||||
random = random
|
random = random
|
||||||
|
mode = mode
|
||||||
update = update
|
update = update
|
||||||
list_file = listfile
|
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!
|
new_version_found = <h3>Update Available!</h3> New version of botamusique is available, send <i>!update</i> to update!
|
||||||
start_updating = Start updating...
|
start_updating = Start updating...
|
||||||
file_missed = Music file '{file}' missed! This item has been removed from the playlist.
|
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>
|
help = <h3>Commands</h3>
|
||||||
<b>Control</b>
|
<b>Control</b>
|
||||||
@ -192,6 +196,8 @@ help = <h3>Commands</h3>
|
|||||||
<li> <b>!<u>st</u>op </b> - stop playing </li>
|
<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>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>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>!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>!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>
|
<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))
|
logging.info("web: delete from playlist: " + util.format_debug_song_string(music))
|
||||||
|
|
||||||
if var.playlist.length() >= int(request.form['delete_music']):
|
if var.playlist.length() >= int(request.form['delete_music']):
|
||||||
if int(request.form['delete_music']) == var.playlist.current_index:
|
index = int(request.form['delete_music'])
|
||||||
var.playlist.remove(int(request.form['delete_music']))
|
|
||||||
if var.botamusique.is_playing and not var.botamusique.is_pause:
|
if index == var.playlist.current_index:
|
||||||
var.botamusique.stop()
|
var.playlist.remove(index)
|
||||||
var.botamusique.launch_music(int(request.form['delete_music']))
|
|
||||||
|
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:
|
else:
|
||||||
var.playlist.remove(int(request.form['delete_music']))
|
var.playlist.remove(index)
|
||||||
|
|
||||||
|
|
||||||
elif 'play_music' in request.form:
|
elif 'play_music' in request.form:
|
||||||
@ -254,8 +264,12 @@ def post():
|
|||||||
action = request.form['action']
|
action = request.form['action']
|
||||||
if action == "randomize":
|
if action == "randomize":
|
||||||
var.botamusique.stop()
|
var.botamusique.stop()
|
||||||
var.playlist.randomize()
|
var.playlist.set_mode("random")
|
||||||
var.botamusique.resume()
|
var.botamusique.resume()
|
||||||
|
if action == "one-shot":
|
||||||
|
var.playlist.set_mode("one-shot")
|
||||||
|
if action == "loop":
|
||||||
|
var.playlist.set_mode("loop")
|
||||||
elif action == "stop":
|
elif action == "stop":
|
||||||
var.botamusique.stop()
|
var.botamusique.stop()
|
||||||
elif action == "pause":
|
elif action == "pause":
|
||||||
@ -269,12 +283,14 @@ def post():
|
|||||||
var.botamusique.volume_set = var.botamusique.volume_set + 0.03
|
var.botamusique.volume_set = var.botamusique.volume_set + 0.03
|
||||||
else:
|
else:
|
||||||
var.botamusique.volume_set = 1.0
|
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))
|
logging.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
|
||||||
elif action == "volume_down":
|
elif action == "volume_down":
|
||||||
if var.botamusique.volume_set - 0.03 > 0:
|
if var.botamusique.volume_set - 0.03 > 0:
|
||||||
var.botamusique.volume_set = var.botamusique.volume_set - 0.03
|
var.botamusique.volume_set = var.botamusique.volume_set - 0.03
|
||||||
else:
|
else:
|
||||||
var.botamusique.volume_set = 0
|
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))
|
logging.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
|
||||||
|
|
||||||
if(var.playlist.length() > 0):
|
if(var.playlist.length() > 0):
|
||||||
|
@ -6,12 +6,20 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
class PlayList(list):
|
class PlayList(list):
|
||||||
current_index = 0
|
current_index = -1
|
||||||
version = 0 # increase by one after each change
|
version = 0 # increase by one after each change
|
||||||
|
mode = "one-shot" # "loop", "random"
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*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):
|
def append(self, item):
|
||||||
self.version += 1
|
self.version += 1
|
||||||
item = util.get_music_tag_info(item)
|
item = util.get_music_tag_info(item)
|
||||||
@ -45,15 +53,27 @@ class PlayList(list):
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
self.version += 1
|
|
||||||
if len(self) == 0:
|
if len(self) == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.version += 1
|
||||||
logging.debug("playlist: Next into the queue")
|
logging.debug("playlist: Next into the queue")
|
||||||
|
|
||||||
self.current_index = self.next_index()
|
if self.current_index < len(self) - 1:
|
||||||
|
self.current_index += 1
|
||||||
return self[self.current_index]
|
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):
|
def update(self, item, index=-1):
|
||||||
self.version += 1
|
self.version += 1
|
||||||
@ -116,8 +136,8 @@ class PlayList(list):
|
|||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.version += 1
|
self.version += 1
|
||||||
self.current_index = 0
|
self.current_index = -1
|
||||||
self.clear()
|
super().clear()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
var.db.remove_section("playlist_item")
|
var.db.remove_section("playlist_item")
|
||||||
|
34
mumbleBot.py
34
mumbleBot.py
@ -90,8 +90,6 @@ class MumbleBot:
|
|||||||
root.setLevel(logging.ERROR)
|
root.setLevel(logging.ERROR)
|
||||||
logging.error("Starting in ERROR loglevel")
|
logging.error("Starting in ERROR loglevel")
|
||||||
|
|
||||||
var.playlist = PlayList()
|
|
||||||
|
|
||||||
var.user = args.user
|
var.user = args.user
|
||||||
var.music_folder = var.config.get('bot', 'music_folder')
|
var.music_folder = var.config.get('bot', 'music_folder')
|
||||||
var.is_proxified = var.config.getboolean(
|
var.is_proxified = var.config.getboolean(
|
||||||
@ -100,7 +98,6 @@ class MumbleBot:
|
|||||||
self.nb_exit = 0
|
self.nb_exit = 0
|
||||||
self.thread = None
|
self.thread = None
|
||||||
self.thread_stderr = None
|
self.thread_stderr = None
|
||||||
self.is_playing = False
|
|
||||||
self.is_pause = False
|
self.is_pause = False
|
||||||
self.playhead = -1
|
self.playhead = -1
|
||||||
self.song_start_at = -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
|
util.pipe_no_wait(pipe_rd) # Let the pipe work in non-blocking mode
|
||||||
self.thread_stderr = os.fdopen(pipe_rd)
|
self.thread_stderr = os.fdopen(pipe_rd)
|
||||||
self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=480)
|
self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=480)
|
||||||
self.is_playing = True
|
|
||||||
self.is_pause = False
|
self.is_pause = False
|
||||||
self.song_start_at = -1
|
self.song_start_at = -1
|
||||||
self.playhead = 0
|
self.playhead = 0
|
||||||
@ -560,11 +556,7 @@ class MumbleBot:
|
|||||||
|
|
||||||
if self.thread is None or not raw_music:
|
if self.thread is None or not raw_music:
|
||||||
# Not music into the buffet
|
# Not music into the buffet
|
||||||
if self.is_playing:
|
if not self.is_pause and var.playlist.next():
|
||||||
# get next music
|
|
||||||
self.is_playing = False
|
|
||||||
if not self.is_pause and len(var.playlist) > 0:
|
|
||||||
var.playlist.next()
|
|
||||||
self.launch_music()
|
self.launch_music()
|
||||||
self.async_download_next()
|
self.async_download_next()
|
||||||
|
|
||||||
@ -610,35 +602,38 @@ class MumbleBot:
|
|||||||
self.thread.kill()
|
self.thread.kill()
|
||||||
self.thread = None
|
self.thread = None
|
||||||
var.playlist.clear()
|
var.playlist.clear()
|
||||||
self.is_playing = False
|
|
||||||
logging.info("bot: music stopped. playlist trashed.")
|
logging.info("bot: music stopped. playlist trashed.")
|
||||||
|
|
||||||
def stop(self):
|
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
|
# Kill the ffmpeg thread
|
||||||
if self.thread:
|
if self.thread:
|
||||||
self.thread.kill()
|
self.thread.kill()
|
||||||
self.thread = None
|
self.thread = None
|
||||||
self.is_playing = False
|
|
||||||
self.is_pause = True
|
|
||||||
self.song_start_at = -1
|
self.song_start_at = -1
|
||||||
self.playhead = 0
|
|
||||||
var.playlist.next()
|
|
||||||
logging.info("bot: music stopped.")
|
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
# Kill the ffmpeg thread
|
# Kill the ffmpeg thread
|
||||||
if self.thread:
|
if self.thread:
|
||||||
self.thread.kill()
|
self.thread.kill()
|
||||||
self.thread = None
|
self.thread = None
|
||||||
self.is_playing = False
|
|
||||||
self.is_pause = True
|
self.is_pause = True
|
||||||
self.song_start_at = -1
|
self.song_start_at = -1
|
||||||
logging.info("bot: music paused at %.2f seconds." % self.playhead)
|
logging.info("bot: music paused at %.2f seconds." % self.playhead)
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
self.is_playing = True
|
|
||||||
self.is_pause = False
|
self.is_pause = False
|
||||||
|
|
||||||
|
if var.playlist.current_index == -1:
|
||||||
|
var.playlist.next()
|
||||||
|
|
||||||
music = var.playlist.current_item()
|
music = var.playlist.current_item()
|
||||||
|
|
||||||
if music['type'] == 'radio' or self.playhead == 0 or not self.check_item_path_or_remove():
|
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',
|
parser.add_argument("--config", dest='config', type=str, default='configuration.ini',
|
||||||
help='Load configuration from this file. Default: configuration.ini')
|
help='Load configuration from this file. Default: configuration.ini')
|
||||||
parser.add_argument("--db", dest='db', type=str,
|
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",
|
parser.add_argument("-q", "--quiet", dest="quiet",
|
||||||
action="store_true", help="Only Error logs")
|
action="store_true", help="Only Error logs")
|
||||||
@ -725,9 +720,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
var.dbfile = args.db
|
|
||||||
config = configparser.ConfigParser(interpolation=None, allow_no_value=True)
|
config = configparser.ConfigParser(interpolation=None, allow_no_value=True)
|
||||||
parsed_configs = config.read(['configuration.default.ini', args.config], encoding='utf-8')
|
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:
|
if len(parsed_configs) == 0:
|
||||||
logging.error('Could not read configuration from file \"{}\"'.format(
|
logging.error('Could not read configuration from file \"{}\"'.format(
|
||||||
@ -753,6 +748,7 @@ if __name__ == '__main__':
|
|||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
root.addHandler(handler)
|
root.addHandler(handler)
|
||||||
|
|
||||||
|
var.playlist = PlayList() # playlist should be initialized after the database
|
||||||
var.botamusique = MumbleBot(args)
|
var.botamusique = MumbleBot(args)
|
||||||
command.register_all_commands(var.botamusique)
|
command.register_all_commands(var.botamusique)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user