diff --git a/source/main.rb b/source/main.rb index 3128c73..0a0bb56 100644 --- a/source/main.rb +++ b/source/main.rb @@ -53,6 +53,7 @@ module Vrobot4 end end +Thread.abort_on_exception = true Vrobot4.main open("bot.yml") ## EOF diff --git a/source/modules/mod_audio.rb b/source/modules/mod_audio.rb index e9f3407..377e36b 100644 --- a/source/modules/mod_audio.rb +++ b/source/modules/mod_audio.rb @@ -6,29 +6,41 @@ require 'streamio-ffmpeg' # @!visibility private class QueueItem - def initialize uri + attr_accessor :next, :prev + attr_reader :time, :ptime, :size, :fname, :descr, :is_stream, :is_file + + def initialize uri, stream = false + @is_stream = stream if uri.scheme == "file" @fname = uri.path - elsif uri.host and uri.host.include? "youtube.com" + @is_file = true + elsif uri.host and uri.host.end_with? "youtube.com" or + uri.host.end_with? "youtu.be" @fname = get_yt_vid_from uri + @is_file = true else @fname = uri.to_s + @is_file = false end - if (mov = FFMPEG::Movie.new(@fname)) - @time = mov.duration - @size = mov.size - else - throw ArgumentError, "File is invalid" + @descr = uri.to_s + + unless @is_stream + if (mov = FFMPEG::Movie.new(@fname)) and mov.valid? + @time = mov.duration + @size = mov.size + @ptime = "%.2i:%.2i" % [@time / 60, @time % 60] + else + throw ArgumentError, "File is invalid" + end end end - def get_file + def file open @fname end def cleanup - Vrobot4.log :DEBUGV, "cleaning up audio cruft" File.delete @tmpf if @tmpf end @@ -59,45 +71,157 @@ class Mod_Audio < Vrobot4::Module::Module def initialize info super - register :c_summon, "summon", "Brings the bot to your voice channel." - register :c_vanquish, "vanquish", "Kicks the bot from voice.", roles: "o" - register :c_play, "play", "Overwrites the queue.", roles: "o" - register :c_queue, "queue", "Adds an item to the queue." - @queue = [] + register :c_join, "voicejoin", "Brings the bot to your voice channel." + register :c_quit, "voicequit", "Kicks the bot from voice.", roles: "h" + register :c_queue, "queue", "Adds an item to the queue or lists it." + register :c_volume, "volume", "Sets the bot's volume." + register :c_skip, "skip", "Skips to the next song." + register :c_pause, "pause", "Pauses the current song." + register :c_play, "play", "Resumes the current song." + register :c_rmqueue, "rmqueue", "Removes an item from the queue." + # TODO: add stream playing command + @queue = [] + @queue_mtx = Mutex.new + @running = false end - def c_summon m, argv - m.serv.voice_join m + def c_join m, argv + vc = m.user.real.voice_channel + raise RuntimeError, "You're not in a voice channel" unless vc + m.serv.bot.voice_connect vc end - def c_vanquish m, argv - m.serv.voice_quit m - end - - def c_play m, argv - @queue.clear - push_queue m, argv - start_playback m, qi + def c_quit m, argv + m.serv.voice_quit end def c_queue m, argv - push_queue m, argv - start_playback m, qi unless m.serv.is_playing? m + if argv.empty? + list_queue m + elsif push_queue m, argv and (@queue.size == 1 or not @running) + start_playback m, 0 + end + end + + def c_rmqueue m, argv + begin + @queue_mtx.lock + rm_queue @queue[argv.to_i + 1] + ensure + @queue_mtx.unlock + end + end + + def c_volume m, argv + num = argv.to_f + num = num > 1 ? 1 : num < 0.1 ? 0.1 : num + m.serv.bot.voice(m.chan.real).volume = num + end + + def c_skip m, argv + m.serv.bot.voice(m.chan.real).stop_playing + end + + def c_pause m, argv + m.serv.bot.voice(m.chan.real).pause + end + + def c_play m, argv + m.serv.bot.voice(m.chan.real).play end private - def push_queue m, argv - m.reply "Queueing '" + uri.to_s + "'..." - - uri = URI.parse argv - qi = @queue.push QueueItem.new(uri) - - m.reply "'" + uri.to_s + "' loaded (%i sec, %i bytes)" % - [qi.time, qi.size] + def list_queue m + begin + @queue_mtx.lock + text = "" + i = 1 + for qi in @queue + text << "%i - <%s> (%s" % [i, qi.descr, qi.ptime] + text << ", %i bytes" % [qi.size] unless qi.is_stream + text << ")\n" + i += 1 + end + if text.empty? then m.reply "No items in queue." + else m.reply_b text end + ensure + @queue_mtx.unlock + end end - def start_playback m, qi - m.serv.voice_play m, qi.get_file + def push_queue m, argv, stream = false + uri = URI.parse argv + + m.reply "Queueing <" + uri.to_s + ">..." + m.chan.real.start_typing if m.serv.class.type == "Discord" + + begin + qi = QueueItem.new(uri, stream) + begin + @queue_mtx.lock + if @queue.any? + @queue.last.next = qi + qi.prev = @queue.last + end + @queue.push qi + ensure + @queue_mtx.unlock + end + if qi.is_stream + m.reply "Stream <" + uri.to_s + "> queued" + else + m.reply "<" + uri.to_s + "> loaded (%s, %i bytes)" % + [qi.ptime, qi.size] + end + return true + rescue + p $! + m.reply "Couldn't load queue item" + return false + end + end + + def rm_queue qi + @queue.delete qi + qi.prev.next = qi.next if qi.prev + qi.next.prev = qi.prev if qi.next + qi.cleanup + end + + def start_playback m, start + @running = true + + begin + @queue_mtx.lock + qi = @queue[start] + ensure + @queue_mtx.unlock + end + + Thread.new do + loop do + begin + if qi.is_file then m.serv.voice_play_io m, qi.file + else m.serv.voice_play m, qi.fname end + rescue + p $! + m.reply "Error playing queue item." + end + + begin + @queue_mtx.lock + rm_queue qi + ensure + @queue_mtx.unlock + end + + unless (qi = qi.next) + m.reply "End of queue reached." + @running = false + break + end + end + end end end