Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0.2
3.2.2
6 changes: 3 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '3.0.2'
ruby '3.2.2'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 7.0'
# Use pg as the database for Active Record
gem 'pg', '~> 1.1'
gem 'pg', '~> 1.4'
# Use Puma as the app server
gem 'puma', '~> 5.0'
# Use SCSS for stylesheets
Expand All @@ -26,7 +26,7 @@ gem 'stimulus-rails'
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.4', require: false
gem 'bootsnap', '>= 1.18', require: false

group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
Expand Down
16 changes: 7 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
bindex (0.8.1)
bootsnap (1.7.1)
msgpack (~> 1.0)
bootsnap (1.18.3)
msgpack (~> 1.2)
builder (3.2.4)
byebug (11.1.3)
capybara (3.35.3)
Expand Down Expand Up @@ -110,7 +110,7 @@ GEM
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.15.0)
msgpack (1.4.2)
msgpack (1.7.2)
net-imap (0.2.3)
digest
net-protocol
Expand All @@ -130,9 +130,7 @@ GEM
nokogiri (1.13.3)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.3-x86_64-darwin)
racc (~> 1.4)
pg (1.2.3)
pg (1.5.6)
public_suffix (4.0.6)
puma (5.2.1)
nio4r (~> 2.0)
Expand Down Expand Up @@ -225,13 +223,13 @@ PLATFORMS
x86_64-darwin-18

DEPENDENCIES
bootsnap (>= 1.4.4)
bootsnap (>= 1.18)
byebug
capybara (>= 3.26)
importmap-rails
jbuilder (~> 2.7)
listen (~> 3.3)
pg (~> 1.1)
pg (~> 1.4)
puma (~> 5.0)
rails (~> 7.0)
redis (~> 4.0)
Expand All @@ -245,7 +243,7 @@ DEPENDENCIES
webdrivers

RUBY VERSION
ruby 3.0.2p107
ruby 3.2.2p53

BUNDLED WITH
2.2.20
34 changes: 4 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,6 @@
# WebRTC + Hotwire + Ruby on Rails
An basic video chat app using the [WebRTC Perfect Negotiation pattern](https://w3c.github.io/webrtc-pc/#perfect-negotiation-example), a sprinkling of Hotwire (mainly [Turbo Streams](https://turbo.hotwire.dev/reference/streams) & [Stimulus](https://stimulus.hotwire.dev/)), and backed by Ruby on Rails.
We've shifted over here:
https://gitlab.com/phrug/hybrid-meetup-experiment.git

## How does it work?
The Stimulus room controller handles Enter button click. It gets the user's local audio and video and feeds them into the local video element. It also starts Action Cable subscriptions specific to the current room: one for communicating WebRTC messages: the `Signaller`; and one for clients to ping others: the `RoomSubscription`. Once connected, the room channel broadcasts the presence of this new client. Each connected client then "greets" this newcomer by calling the `greet` action on the room channel and specifying `to` and `from` fields.

The `greet` action broadcasts specifically to the newcomer, a Turbo Stream update which appends a media element representing the remote client. It's important we do this first so that it's ready for incoming streams. The medium controller messages the room controller to notify that it has been added. This begins the WebRTC negotiation to form a connection between the two clients.

The WebRTC negotiation is quite complex, and even though things are required to happen in a particular order, responses are triggered asynchronously, making it tricky to get right. This is where the [WebRTC Perfect Negotiation pattern](https://w3c.github.io/webrtc-pc/#perfect-negotiation-example) comes in. We won't go into it too much here, as it's covered well elsewhere; but for the purpose of this description, (_deep breath_), Session Description Protocol (SDP) descriptions and Interactive Connectivity Establishment (ICE) candidates are exchanged over the `Signaller`. The negotiation is "perfect" as it ensures that the `RTCPeerConnection` is in the correct state for setting descriptions, and avoids collision errors by having one client be "polite" and other "impolite"—the polite one backs down in the case of a collision. Anyway, the code for this has been mostly lifted from the spec and handled by the `Signaller`/`WebrtcNegotiation`. When creating the negotition, the room controller listens for `track` events on the `RTCPeerConnection`, so it can start streaming from the remote client.

Once the negotiation has been created, the newcomer client notifies the other client by reciprocating the greeting i.e. calling `greet` on the room channel. This triggers the same process as above on the other client: appending a media element via a Turbo Stream update, and starting a WebRTC negotiation. This time though, instead of a greeting (which would be very polite, but awkward and endless!), the client starts streaming to the other client, by adding media tracks to the connection. This kicks off the complex exchange of SDP descriptions and ICE candidates, and once either is received by the newcomer, it can start the streaming process from its own stream to the other client. Now all clients are streaming to and from each other.

Finally, when a client disconnects from the room channel, a Turbo Stream update removes the media element from the screen. The media controller broadcasts its removal so the room controller can clean up.

To summarise the flow:

1. A newcomer broadcasts its presence to others in the room
2. The connected clients greet this newcomer letting them know their ID
3. A Turbo Stream update creates a video element on the newcomer's screen for each greeting it receives
4. The newcomer creates a WebRTC negotiation and sends a greeting back to each of the other clients
5. Turbo Stream updates create video elements on each of the other clients' screens
6. WebRTC negotiations are created by each of the other clients, and they start streaming to the newcomer
7. Reacting to negotiation activity (SDP descriptions and ICE candidate exchanges), the newcomer starts streaming to other clients

## Browser Support?
This has only been tested in macOS Firefox/Chrome/Safari and iOS Safari.

## TODO
- Add "Leave" functionality + handle ICE candidate disconnections (rather than just closing the browser window)

## License
Copyright © 2021+ Dom Christie and released under the MIT license.
Originally forked from here:
https://github.com/domchristie/webrtc-hotwire-rails