Skip to content

Instantly share code, notes, and snippets.

@pachacamac
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pachacamac/aa80aced80f6b0c42ef6 to your computer and use it in GitHub Desktop.
Save pachacamac/aa80aced80f6b0c42ef6 to your computer and use it in GitHub Desktop.
multicast ip music player
#####################################################################################
#
# Peer base
#
#####################################################################################
require 'socket'
require 'thread'
require 'ipaddr'
require 'json'
class Peer
MULTICAST_ADDR = '224.5.3.209'
BIND_ADDR = '0.0.0.0'
PORT = 53209
MULTICAST_BIND_ADDR = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new(BIND_ADDR).hton
def initialize
@user_info = {
:user => ENV['USERNAME'] || ENV['USER'],
:host => Socket.gethostname,
:ip => (Socket.ip_address_list.detect{|i| i.ipv4_private?}.ip_address rescue '?'),
:pid => $$
}
@handle = [@user_info[:user], @user_info[:host], @user_info[:ip], @user_info[:pid]].join('/')
@listeners = []
end
def add_message_listener(listener)
listen unless @listening
@listeners << listener
end
def send(data)
data = {'message' => data} unless data.is_a?(Hash)
data.merge!('handle' => @handle)
puts "sending #{data}" if $DEBUG
socket.send(data.to_json, 0, MULTICAST_ADDR, PORT)
data
end
private
def listen
socket.bind(BIND_ADDR, PORT)
Thread.new do
loop do
data, _ = socket.recvfrom(1024)
data = JSON.parse(data)
puts "received #{data}" if $DEBUG
@listeners.each{|listener| listener.new_message(data) } unless data['handle'] == @handle
end
end
@listening = true
end
def socket
@socket ||= UDPSocket.open.tap do |socket|
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, MULTICAST_BIND_ADDR)
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 3)
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
end
end
end
#####################################################################################
#
# Player
#
#####################################################################################
class MusicPlayer
#MUSIC_PATH = File.expand_path(File.join(File.dirname(__FILE__),'music'))
MUSIC_PATH = File.expand_path(File.dirname(__FILE__))
def play(file)
stop if @pid
file = File.expand_path(File.join(MUSIC_PATH, file))
return false unless File.exists? file
@pid = fork do
if RUBY_PLATFORM =~ /darwin/
exec 'afplay', file
else
#exec 'mpg123','-T' ,'-q', file
exec 'mpg123','-q', file
end
end
end
def say(text)
stop if @pid
@pid = fork do
if RUBY_PLATFORM =~ /darwin/
exec "say '#{text.tr("'","")}'"
else
exec "echo '#{text.tr("'","")}' | festival --tts"
end
end
end
def stop
return unless @pid
Process.kill 'TERM', @pid
@pid = nil
end
def list
Dir.entries(MUSIC_PATH).reject{|e|e.start_with? '.'}.sort
end
end
#####################################################################################
#
# Interface
#
#####################################################################################
require 'curses'
class Window
include Curses
def initialize(client)
@client = client
@messages = []
@player = MusicPlayer.new
end
def start
init_screen
start_color
init_pair(COLOR_WHITE, COLOR_BLACK, COLOR_WHITE)
use_default_colors
redraw
@client.add_message_listener(self)
loop do
content = getstr
if content.length > 0
message = @client.send(content)
new_message(message)
end
end
end
def new_message(message)
if message['message'].start_with?('play ')
@player.play(message['message'][5..-1])
elsif message['message'] == 'stop'
@player.stop
elsif message['message'] == 'list'
@player.list.each{|e| @client.send(e)}
elsif message['message'].start_with?('say ')
@player.say(message['message'][4..-1])
end
@messages << message
redraw
end
private
def redraw
draw_text_field
draw_messages
cursor_to_input_line
refresh
end
def draw_text_field
setpos(divider_line, 0)
attron(color_pair(COLOR_WHITE) | A_NORMAL){ addstr(" [beat2beat]" + " " * cols) }
cursor_to_input_line
clrtoeol
end
def draw_messages
@messages.last(window_line_size).inject(0) do |line_number, message|
setpos(line_number, 0)
clrtoeol
addstr("<#{message['handle']}> #{message['message']}")
line_number + 1
end
end
def input_line; lines - 1 end
def divider_line; lines - 2 end
def window_line_size; lines - 2 end
def cursor_to_input_line; setpos(input_line, 0) end
end
Window.new(Peer.new).start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment