On Github palavatv / talk-rugb-palava-machine
Ruby User Group Berlin | October 10, 2013
<video id="gum-video" autoplay="autoplay"> <script> navigator.webkitGetUserMedia( {video: true, audio: false}, function(stream) { document.getElementById('gum-video').src = webkitURL.createObjectURL(stream); } ); </script> </video>
Signaling server written in Ruby
Talks WebSockets via em-websockets
Allows client to join a room to find other clients
A few defined JSON messages
join_room(room_id, status)
send_to_peer(peer_id, data)
update_status(status)
joined_room(me, peers)
peer_updated_status(peer_id, peer_status)
error(id, message)
shutdown(seconds)
offer(sdp)
answer(sdp)
ice_candidate(...)
EM.run{ em_init trap(:TERM){ em_sigterm } trap(:INT){ em_sigint } EM::WebSocket.run(options){ |ws| ws.onopen{ |handshake| ws_open(ws, handshake) } ws.onmessage{ |message| ws_message(ws, message) } ws.onclose{ |why| ws_close(ws, why) } ws.onerror{ |error| ws_error(ws, error) } EM.error_handler{ |e| em_error(e) } } }
def ws_message(ws, message) ws_message_action(ws, ws_message_parse(ws, message)) rescue MessageParsingError, MessageError => e send_error(ws, e) end
def ws_message_parse(ws, message) connection_id = manager.connections.get_connection_id(ws) or raise MessageError.new(ws), 'unknown connection' ClientMessage.new(message, connection_id) end
def ws_message_action(ws, message_event) manager.debug "#{message_event.connection_id} <#{message_event.name}>" manager.public_send( message_event.name, message_event.connection_id, *message_event.arguments ) end
Stores in which room a connection resides
Stores a connection ids per open socket
Uses asynchronous em-hiredis driver
def join_room(connection_id, room_id, status) return_error connection_id, 'no room id given' if !room_id return_error connection_id, 'room id too long' if room_id.size > 50 # ... info "#{connection_id} joins ##{room_id[0..10]}... #{status}" script_join_room(connection_id, room_id, status){ |members| # ...
def script_join_room(connection_id, room_id, status, &block) @redis.eval \ SCRIPT_JOIN_ROOM, 4, "store:room:members:#{room_id}", "store:room:peak_members:#{room_id}", "store:connection:joined:#{connection_id}", "store:connection:room:#{connection_id}", connection_id, PAYLOAD_NEW_PEER[connection_id, status], Time.now.getutc.to_i, room_id, &block end
local members = redis.call('smembers', KEYS[1]) local count = 0 for _, peer_id in pairs(members) do redis.call('publish', "ps:connection:" .. peer_id, ARGV[2]) count = count + 1 end redis.call('sadd', KEYS[1], ARGV[1]) if count == 0 or tonumber(redis.call('get', KEYS[2])) <= count then redis.call('set', KEYS[2], count + 1) end redis.call('set', KEYS[3], ARGV[3]) redis.call('set', KEYS[4], ARGV[4]) return members
Using redis' PubSub feature
Allows for multiple EM socket servers running simultanously
Every socket connection listens on its own redis queue
def announce_connection(ws) connection_id = @connections.register_connection(ws) info "#{connection_id} <open>" @subscriber.subscribe "ps:connection:#{connection_id}" do |payload| ws.send_text(payload) end end
# ... unless %w[offer answer ice_candidate].include? data['event'] return_error connection_id, 'event not allowed' end @publisher.publish "ps:connection:#{peer_id}", (data || {}).merge("sender_id" => connection_id).to_json
Extendable Plugin Architecture
General Usage Stats
signalmaster
webrtc.io
together.js hub
peerjs server
We are working on a webrtc signaling service. You might want to check it out if you consider using webrtc in a future project.
More palava Information
Signaling Service