Last active
August 29, 2015 14:02
-
-
Save pachacamac/aa80aced80f6b0c42ef6 to your computer and use it in GitHub Desktop.
multicast ip music player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
##################################################################################### | |
# | |
# 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