require 'optparse' require 'json' require 'open3' require 'io/console' require 'mp3info' require 'mikeplayer/version' require 'mikeplayer/settings' require 'mikeplayer/playlist' require 'mikeplayer/song' module MikePlayer class Player PAUSE_INDICATOR = " ||".freeze SLEEP_SETTING = 0.5 STOPPED = :stopped PLAYING = :playing PAUSED = :paused def initialize(options, *args) @settings = Settings.new(options) @playlist = Playlist.new(@settings.playlist) @minutes = @settings.minutes @command = '' @pid = nil @timer_start = nil @state = STOPPED check_system if (true == @settings.list?) @songs.map { |song| File.basename(song) }.sort.each {|song| puts "#{File.basename(song)}"} exit 0 end args.flatten.each do |arg| @playlist.find_song(arg, @settings.music_dir) end if (true == @settings.random?) @playlist.add_random(@settings.random, @settings.music_dir) end @playlist.save end def play @playlist.shuffle! if @settings.shuffle? print "Playlist #{@playlist.info}\n" if @playlist.finished? puts "No songs in playlist." exit 1 end @thread = Thread.new do display_width = 0 while (false == @playlist.finished?) song = @playlist.current @song_start = Time.now info_prefix = "\r#{@playlist.current_song_info}".freeze stdin, stdother, thread_info = Open3.popen2e('play', '--no-show-progress', '--volume', @settings.volume, song.filename) @state = PLAYING @pid = thread_info.pid indicator = '' info_changed = false while (true == pid_alive?) pause_if_over_time_limit if (true == playing?) indicator = "#{'>' * (playing_time % 4)}" info_changed = true elsif (true == paused?) && (PAUSE_INDICATOR != indicator) indicator = PAUSE_INDICATOR info_changed = true end if (true == info_changed) mindicator = "" if (0 < minutes_remaining) mindicator = "(#{minutes_remaining}↓) " end info = "#{info_prefix} #{song.length_str(playing_time)} #{mindicator}#{indicator}".ljust(display_width) display_width = info.size print(info) info_changed = false $stdout.flush end sleep SLEEP_SETTING end stdin.close stdother.close @pid = nil if (true == playing?) next_song end end @pid = nil exit end wait_on_user puts "" end def cmd_exist?(cmd) if (true != system('command')) raise "Missing 'command' command, which is used to test compatibility." end if (true != system("command -v #{cmd} >/dev/null 2>&1")) return false end return true end private def check_system %w(play).each do |cmd| if (false == cmd_exist?(cmd)) raise "#{cmd} failed, do you have sox installed?" end end return nil end def wait_on_user while ('q' != @command) @command = STDIN.getch if ('c' == @command) press_pause elsif ('v' == @command) next_song elsif ('z' == @command) previous_song elsif ('q' == @command) && (false == @pid.nil?) stop_song @thread.kill elsif ('t' == @command) @timer_start = Time.now elsif (false == @timer_start.nil?) && ("#{@command.to_i}" == @command) if (0 == @minutes) @minutes = @command.to_i else @minutes *= 10 @minutes += @command.to_i end end end end def playing? return (PLAYING == @state) end def paused? return (PAUSED == @state) end def press_pause if (true == playing?) kill("STOP") @pause_time = Time.now @state = PAUSED elsif (true == paused?) kill("CONT") @song_start += (Time.now - @pause_time) @pause_time = nil @state = PLAYING else print("Confused state #{@state}.") end end def stop_song if (true == paused?) kill("CONT") end kill("INT") sleep 0.2 if (true == pid_alive?) kill("KILL") end @state = STOPPED end def pid_alive?(pid = @pid) if (false == pid.nil?) return system("ps -p #{pid} > /dev/null") end return false end def next_song stop_song @playlist.next end def previous_song stop_song @playlist.previous end def kill(signal) if (false == @pid.nil?) Process.kill(signal, @pid) end end def pause_if_over_time_limit if (false == @timer_start.nil?) && (0 < @minutes) && (true == playing?) if (0 > minutes_remaining) press_pause @timer_start = nil @minutes = 0 end end end def playing_time return (Time.now - @song_start).to_i - pause_time end def pause_time if (@pause_time.nil?) return 0 else return (Time.now - @pause_time).to_i end end def minutes_remaining if ((0 == @minutes) || (@timer_start.nil?)) return -1 else return (@minutes - ((Time.now - @timer_start).to_i / 60).to_i) end end end end