Skip to content

Instantly share code, notes, and snippets.

@Phrogz
Created January 14, 2016 22:18
Show Gist options
  • Save Phrogz/0a6e70257f22d7cb2f2a to your computer and use it in GitHub Desktop.
Save Phrogz/0a6e70257f22d7cb2f2a to your computer and use it in GitHub Desktop.
Faye + Sinatra inside EventMachine
<!DOCTYPE html>
<html lang='en'><head>
<meta charset='utf-8'>
<title>RB3Jay</title>
<script src='/jquery.min.js'></script>
<script src='/rb3jay.js'></script>
<script src='/faye/client.js'></script>
</head><body>
<div id='playing'>
<button id='toggle'>play/pause</button>
<button id='skip'>next song</button>
<div id='song'>…</div>
<div id='volume'><input max='100' min='0' type='range'></div>
</div>
<table id='upnext'>
<caption>up next</caption>
<thead><tr><th>title</th><th>artist</th></tr></thead>
<tbody></tbody>
</table>
<script>
var øserver = new Faye.Client('/faye', { retry:2, timeout:10 } ),
øcontrols = new Controls('#playing'),
øupnext = new LiveQueue('#upnext tbody');
øcontrols.onToggle = function(playing){ $.post('/' + (playing ? 'pause' : 'play')) };
øcontrols.onSkip = function(){ $.post('/skip') };
øserver.subscribe('/status',øcontrols.update.bind(øcontrols));
øserver.subscribe('/next', øupnext.update.bind(øupnext) );
// populate the up-next list immediately after loading
$.get('/next',øupnext.update.bind(øupnext));
</script>
</body></html>
%w[eventmachine thin sinatra faye ruby-mpd].each{ |lib| require lib }
# Sets up ENV variables with default values
# in case they're not already set.
require_relative 'environment'
def run!
EM.run do
# Ensure that Faye can use WebSockets with the Thin server
Faye::WebSocket.load_adapter('thin')
webapp = MyWebApp.new
server = Faye::RackAdapter.new(mount:'/', timeout:25)
# Requests to /faye/… are delegated to the Faye server;
# these are generated automatically by the client-side Faye JavaScript.
# Other requests are delegated to the Sinatra application.
dispatch = Rack::Builder.app do
map('/'){ run webapp }
map('/faye'){ run server }
end
Rack::Server.start({
app: dispatch,
Host: ENV['WEBAPP_HOST'],
Port: ENV['WEBAPP_PORT'],
server: 'thin',
signals: false,
})
end
end
class MyWebApp < Sinatra::Application
# http://stackoverflow.com/q/10881594/405017
configure{ set threaded:false }
def initialize
super
# A connection to the Music Player Daemon
@mpd = MPD.new( ENV['MPD_HOST'], ENV['MPD_PORT'] )
@mpd.connect
# A Faye server-side client, used to push messages to connected web browsers
@faye = Faye::Client.new("http://#{ENV['WEBAPP_HOST']}:#{ENV['WEBAPP_PORT']}/faye")
# Every second, send the status and/or up-next list (if they have changed)
EM.add_periodic_timer(1) do
if (info=status) != @last_info
send_status( @last_info=info )
end
# This may be slow (many seconds)
if (songs=up_next) != @last_songs
send_next( @last_songs=songs )
end
end
end
helpers do
def status
@mpd.status
end
def send_status( info=status )
@faye.publish '/status', info
end
def up_next
# this may take many seconds
@mpd.queue.map(&:details)
end
def send_next( songs=up_next )
@faye.publish '/next', songs
end
end
# Most of the time we send JSON responses
before{ content_type :json }
# Send the main page (which includes JavaScript that boots a client-side Faye connection)
get '/' do
content_type :html
send_file File.expand_path('index.html',settings.public_folder)
end
# Instead of HTTP Post, these could be Faye pushes. Pros/cons unknown.
# (I suppose this allows me to easily record WHO made the change, via sessions.)
post('/play'){ @mpd.play; send_status }
post('/paws'){ @mpd.pause=true; send_status }
post('/skip'){ @mpd.next; send_status; send_next }
post('/seek'){ @mpd.seek params[:time].to_f; send_status }
post('/volm'){ @mpd.volume = params[:volume].to_i; send_status }
# When a web browser connects it needs a fresh copy of the up-next list.
# Using HTTP keeps this update "personal";
# using Faye would re-send the (unchanged) list to everyone for each new browser.
get ('/next'){ up_next.to_json }
end
run!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment