Created
January 14, 2016 22:18
-
-
Save Phrogz/0a6e70257f22d7cb2f2a to your computer and use it in GitHub Desktop.
Faye + Sinatra inside EventMachine
This file contains hidden or 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
<!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> |
This file contains hidden or 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
%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