WebRTC in Ruby on Rails
Table of Contents
What is WebRTC?
WebRTC (Web Real-Time Communication) is a free and open-source technology that allows two users to talk to each other using video, voice, or data directly in their browsers — without needing to install anything.
Think of it like Zoom or Google Meet, but built into your website. It works on most modern browsers and lets users:
- Start a video or audio call directly in the browser
- Share files with other users instantly
- Stream media in real time (live streaming)
WebRTC is designed for peer-to-peer communication. That means once the connection is made, the data goes directly from one person’s device to the other — fast and private.
Why Use WebRTC in a Rails App?
By using WebRTC in your Rails application, you can add real-time communication features without relying on external video platforms or services.
Here’s why it’s useful in Rails:
- No plugins: Users don’t have to install Zoom or Skype
- Free: No cost for the actual video engine
- Secure: Encrypted by default (HTTPS + DTLS/SRTP)
- Works well with Rails ActionCable: Use ActionCable for signaling between users
Whether you’re building a remote doctor consultation app, live tutoring platform, or customer support chat, WebRTC gives you the real-time layer — and Rails gives you everything else.
How WebRTC Works – Core Concepts
WebRTC connects two people directly so they can talk via video, voice, or share data without using a middle server for the actual media. But to do that, some setup is needed behind the scenes.
Here are the key building blocks of how WebRTC works:
1. Media Stream (Camera & Mic)
WebRTC uses your device’s camera and microphone through the browser with JavaScript. The API to access them is getUserMedia()
.
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => { /* send or play stream */ });
2. Peer Connection
A RTCPeerConnection is used to create the connection between two users. It manages everything about the media flow, including quality and network.
3. Signaling (Using Action Cable or WebSocket)
WebRTC can’t create the connection on its own. Browsers need to talk first and share “connection info” like:
- Offer (how to connect)
- Answer (I agree to connect this way)
- ICE Candidates (network addresses)
4. STUN Server
A STUN server helps each browser find its public IP address. This is needed when people are behind routers or firewalls.
5. TURN Server (Backup Plan)
If a direct connection fails (e.g., corporate firewalls), a TURN server is used to relay the media. It’s slower but works as a backup.
6. ICE Framework
ICE (Interactive Connectivity Establishment) is the protocol that tests different ways to connect (using STUN, TURN, local network, etc.) and picks the best one.
7. Data Channels
Besides audio and video, WebRTC can send any data between users using RTCDataChannel. Think of it like sending files or live messages.
Real-Life Example (Simple Flow)
- User A joins the app and clicks “Start Call”
- Their browser captures audio/video and creates an “offer”
- The offer is sent to User B via ActionCable (signaling)
- User B accepts and sends back an “answer”
- Both browsers exchange ICE candidates
- Once connected, video/audio goes directly from A ↔ B
Important: The server (Rails) helps with signaling but does NOT carry the media. That happens directly between users’ browsers.
Difference Between Action Cable and WebRTC
Although both Action Cable and WebRTC are used in real-time applications, they serve completely different purposes.
Feature | Action Cable (Rails) | WebRTC |
---|---|---|
What it is | A WebSocket framework built into Ruby on Rails | A browser-based peer-to-peer media and data communication technology |
Communication type | Client ⇄ Server | Peer ⇄ Peer (Direct browser-to-browser) |
Used for | Real-time messages, chats, notifications | Video calls, audio calls, file sharing, screen sharing |
Media support | Does NOT support audio/video transfer | Supports real-time audio/video/media |
Latency | Low latency, server in-between | Ultra low latency (peer-to-peer) |
Needs a server? | Yes (Rails server with ActionCable) | Only for signaling (initial connection), then direct |
Common use cases | Live chat, notifications, game updates, dashboards | Video calls, telemedicine, live education, screen share |
Browser support | Works anywhere with JS + WebSockets | Modern browsers only (Chrome, Firefox, Safari, etc.) |
How They Work Together
In most WebRTC apps built with Rails, you use Action Cable to exchange connection details (signaling), and then let WebRTC handle the real-time video/audio between users.
For example:
– Action Cable sends the “offer” and “answer” between users
– WebRTC builds the direct video/audio connection between browsers
WebRTC Architecture in Rails
WebRTC handles the real-time media, but your Rails app plays a crucial role in setting up and managing the connection between users. This is where the architecture comes in.
🧠 Key Components
- Client (Browser): Captures camera/microphone and initiates the call.
- Rails Server: Handles authentication, authorization, and room/session setup.
- ActionCable (WebSocket): Acts as a signaling server to exchange connection info between peers.
- STUN/TURN Servers: Help browsers find the best way to connect (especially behind firewalls).
- RTCPeerConnection: Manages the actual media/data connection between users.
📊 High-Level Flow (Rails + WebRTC)
- User A opens the app and starts a video call.
- User A’s browser uses
getUserMedia()
to access video/audio. - User A creates a WebRTC offer and sends it via ActionCable to the Rails server.
- Rails server broadcasts this offer to User B using ActionCable (WebSocket).
- User B receives the offer, creates an answer, and sends it back through ActionCable.
- Both users exchange ICE candidates via ActionCable.
- Once the signaling is complete, a direct P2P WebRTC connection is established between both browsers.
- Media (video/audio) is streamed peer-to-peer without going through Rails.
📦 Folder Structure Example in Rails
app/
├── controllers/
│ └── calls_controller.rb # Handles room auth, redirection
├── channels/
│ └── video_call_channel.rb # Signaling logic via ActionCable
├── views/
│ └── calls/show.html.erb # Loads JS to handle WebRTC
├── javascript/
│ └── channels/video_call.js # WebRTC logic (peer connection, ICE, streams)
🧩 Tech Stack Overview
Component | Tool | Purpose |
---|---|---|
Backend | Ruby on Rails | Manages user flow, sessions |
Realtime Signaling | ActionCable (WebSocket) | Exchange offers/answers/ICE |
Media Capture | JavaScript (getUserMedia) | Access camera/mic |
P2P Connection | RTCPeerConnection | Stream audio/video directly |
Fallback Relay | TURN Server | Send media if P2P fails |
🎯 When to Use This Architecture
- Telehealth platforms (doctor ↔ patient)
- Online interview tools
- Video customer support in apps
- One-on-one or small group tutoring
WebRTC + Rails gives you full control over your real-time communication app without relying on third-party platforms. ActionCable makes it easy to plug signaling into your Rails system.
Key Terms & Vocabulary in WebRTC
When building or debugging WebRTC apps — especially in a Rails + JavaScript setup — you’ll come across several technical terms. Here’s a simple glossary of what each one means and why it matters.
Term | Meaning / Usage |
---|---|
getUserMedia() | A JavaScript method to access the user’s camera and microphone. |
RTCPeerConnection | The core object that manages audio/video/data exchange between two browsers. |
RTCSessionDescription | Describes an offer or answer. Required to negotiate the WebRTC connection. |
ICE (Interactive Connectivity Establishment) | A method to find the best possible path to connect two peers across networks. |
ICE Candidate | A possible network address that can be used to connect the peers (like IP and port). |
Signaling | The process of exchanging offers, answers, and ICE candidates between peers — done via WebSocket or ActionCable in Rails. |
STUN Server | Helps a peer discover its public IP address and port behind NAT. |
TURN Server | Relays media traffic when direct peer-to-peer connection isn’t possible. |
SDP (Session Description Protocol) | A text format used to describe multimedia sessions (codecs, ports, etc.) sent in offer/answer. |
MediaStream | Represents the stream of audio/video tracks from the user’s device. |
track | A single audio or video source within a MediaStream (e.g., microphone or camera). |
DataChannel | Optional feature to send text or binary data peer-to-peer without a server. |
onicecandidate | An event triggered when a new ICE candidate is found. Used to send it to the other peer. |
ontrack | An event that receives the remote stream once the connection is established. |
These terms form the building blocks of any WebRTC app. Understanding them helps you implement, debug, and extend features like video calls, screen sharing, or file transfer efficiently in your Rails projects.
Basic WebRTC Implementation (JavaScript Only)
This example covers just the frontend part — enough to:
- Access the camera and microphone
- Create a peer connection
- Display your own video stream
- Prepare for signaling (offer/answer/candidates) — but not send yet
🔧 HTML Setup
<video id="localVideo" autoplay muted playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
📜 JavaScript (Core WebRTC Setup)
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
let localStream;
let peerConnection;
// STUN server (public one by Google)
const iceConfig = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" }
]
};
// Step 1: Get user media
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
localStream = stream;
localVideo.srcObject = stream;
initializePeerConnection();
})
.catch(error => {
console.error("Error accessing media devices.", error);
});
// Step 2: Create peer connection
function initializePeerConnection() {
peerConnection = new RTCPeerConnection(iceConfig);
// Add local tracks to peer connection
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// Receive remote stream
peerConnection.ontrack = event => {
remoteVideo.srcObject = event.streams[0];
};
// ICE candidate generation (to send to other peer via backend)
peerConnection.onicecandidate = event => {
if (event.candidate) {
console.log("Generated ICE candidate:", event.candidate);
// You would send this to the other user using signaling
}
};
// (Optional) Connection state logging
peerConnection.onconnectionstatechange = () => {
console.log("Connection state:", peerConnection.connectionState);
};
}
✅ What This Does
- Grabs camera/mic using
getUserMedia()
- Creates a WebRTC connection using
RTCPeerConnection
- Attaches local media to the connection
- Prepares to receive a remote stream (to be added later)
Next Step: To complete the connection, you’ll need signaling via backend (e.g., ActionCable in Rails) to exchange:
- Offer
- Answer
- ICE candidates
Example: One-to-One Video Call in Rails
Let’s build a basic one-to-one video call feature using WebRTC and Rails. We’ll use JavaScript for capturing video/audio and ActionCable for signaling between two users.
🔧 Step-by-Step Implementation
1. Create a Call Room
Add a simple controller to manage the call room:
# app/controllers/calls_controller.rb
class CallsController < ApplicationController
def show
@room_id = params[:id]
end
end
2. Create a Route
# config/routes.rb
get '/call/:id', to: 'calls#show'
3. Setup ActionCable Channel
# app/channels/video_call_channel.rb
class VideoCallChannel < ApplicationCable::Channel
def subscribed
stream_from "video_call_#{params[:room]}"
end
def receive(data)
ActionCable.server.broadcast("video_call_#{params[:room]}", data)
end
end
4. Frontend HTML
<video id="localVideo" autoplay muted playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
5. JavaScript: WebRTC + Signaling
// app/javascript/channels/video_call.js
import consumer from "./consumer";
const peer = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
const room = window.location.pathname.split("/").pop();
const channel = consumer.subscriptions.create(
{ channel: "VideoCallChannel", room },
{
received(data) {
if (data.offer) {
peer.setRemoteDescription(new RTCSessionDescription(data.offer));
peer.createAnswer().then(answer => {
peer.setLocalDescription(answer);
channel.send({ answer });
});
} else if (data.answer) {
peer.setRemoteDescription(new RTCSessionDescription(data.answer));
} else if (data.candidate) {
peer.addIceCandidate(new RTCIceCandidate(data.candidate));
}
}
}
);
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
document.getElementById("localVideo").srcObject = stream;
stream.getTracks().forEach(track => peer.addTrack(track, stream));
});
peer.onicecandidate = event => {
if (event.candidate) {
channel.send({ candidate: event.candidate });
}
};
peer.ontrack = event => {
document.getElementById("remoteVideo").srcObject = event.streams[0];
};
// Initial offer (if first user)
peer.createOffer().then(offer => {
peer.setLocalDescription(offer);
channel.send({ offer });
});
✅ How It Works
- User A opens the room → creates an offer
- User B joins the same room → responds with an answer
- ICE candidates are exchanged to complete the connection
- Once connected, browser streams video/audio directly peer-to-peer
📌 Notes
- This example is one-to-one (room shared by 2 users)
- Media never passes through the Rails server — only signaling does
- You can expand this to multiple users with some changes
10 Real-World Use Cases of WebRTC
WebRTC is not just for video calls — it’s a powerful technology used in many real-world applications across industries. Here are ten practical examples:
- Telemedicine
Doctors and patients can talk face-to-face from anywhere using secure, real-time video sessions — reducing in-person visits and improving remote care. - Online Education & Tutoring
Teachers and students connect through virtual classrooms with live video, screen sharing, and even whiteboarding tools. - Customer Support Video Chat
Businesses offer live face-to-face video support on their website, improving trust and resolving issues faster. - Remote Interviews
Companies conduct job interviews via WebRTC, often using custom-built Rails apps that include time tracking and candidate evaluations. - Social Media Video Calls
Platforms like Facebook and Snapchat use WebRTC to enable real-time communication between friends — from video calls to filters. - Gaming Voice Chat
In multiplayer online games, WebRTC provides low-latency voice communication for team coordination. - Secure Corporate Video Conferencing
Enterprises build private, in-house video chat systems using WebRTC for internal meetings, reducing reliance on third-party apps like Zoom. - Live Auctions or Sales Events
Bidders can watch and participate in live auctions through video and voice, enhancing engagement in real time. - Real-Time Collaboration Tools
Apps like Miro and Figma use WebRTC for live collaboration, screen sharing, and feedback during real-time design or coding sessions. - Peer-to-Peer File Sharing
WebRTC’s data channels allow users to share large files without uploading to a central server — directly browser to browser.
These use cases show just how flexible WebRTC is — whether you’re building a healthcare platform or adding a simple chat feature to your Rails app, WebRTC helps you go real-time with ease.
Security & Privacy Considerations in WebRTC
WebRTC is designed with security in mind from the start — but that doesn’t mean you can skip implementation best practices. Below are key security and privacy points every developer should know when using WebRTC in a Rails application.
🔒 Built-in WebRTC Security Features
- Encryption by Default: All WebRTC streams (video, audio, and data) are encrypted using DTLS (Datagram Transport Layer Security) and SRTP (Secure Real-time Transport Protocol).
- Secure Transport: WebRTC only works on HTTPS pages or localhost (to prevent man-in-the-middle attacks).
- No Plugin Risks: Since it runs natively in the browser, there are no plugin-based vulnerabilities like Flash or Java applets.
- Permission-Based Media Access: The browser asks users for explicit permission before accessing their microphone or camera.
🚨 Security Considerations for Rails Developers
- Use HTTPS Everywhere: WebRTC will not work on non-secure HTTP. Ensure your Rails app uses SSL.
- Validate User Sessions: Use Rails authentication (Devise or similar) to ensure only authorized users can access call rooms.
- Protect Signaling Channel (ActionCable): Secure WebSocket communication using authentication and encrypted channels.
- Don’t Trust Client Data: Even though media is peer-to-peer, always verify any data or metadata coming through WebSocket signaling.
- Tokenize Rooms or Calls: Use temporary, expiring room tokens so users can’t guess room IDs (e.g., `/call/abc123xyz` instead of `/call/1`).
🕵️ Privacy Best Practices
- Ask Only What You Need: If you don’t need audio, request only video — respect the user’s privacy.
- Use Muted/Hidden Streams Until Consent: Load the camera but don’t show or transmit video until the user confirms.
- Allow Leaving or Disabling Mic/Camera: Let users control their devices anytime during the call.
- Don’t Record Without Consent: Always show a visual cue if recording is taking place, and ask for user permission.
🛡️ Extra Protection (for Production)
- Implement TURN servers with access control (to avoid abuse)
- Use firewalls and rate-limiting on ActionCable/WebSocket endpoints
- Use UUIDs and secure random tokens for all room/session identifiers
- Log connection attempts and disconnections for auditing
With WebRTC, you’re streaming content directly between users — which is great for speed and privacy — but signaling and access control still depend on your Rails backend. Always secure both sides.
WebRTC Interview Questions & Answers
Here are 10 commonly asked WebRTC interview questions with easy-to-understand answers — useful for developers working with real-time features in Rails or any web application.
- What is WebRTC?
A: WebRTC (Web Real-Time Communication) is a browser-based API that enables audio, video, and data communication between peers without needing plugins or external software. It’s used for video calls, screen sharing, and live data transfer. - How does WebRTC establish a connection?
A: WebRTC uses a signaling mechanism to exchange connection details (offer, answer, and ICE candidates). This is usually handled via WebSockets or tools like Rails ActionCable. - What are STUN and TURN servers?
A: STUN servers help a device find its public IP address. TURN servers act as a relay when a direct connection between peers fails (e.g., due to firewalls or NAT). - Is WebRTC media encrypted?
A: Yes. WebRTC uses DTLS and SRTP to encrypt all media and data streams, ensuring secure communication between peers. - What is the role of the signaling server in WebRTC?
A: The signaling server (e.g., using Rails ActionCable) is used to exchange metadata between peers before they connect. It does not transmit media — only messages for connection setup. - Can WebRTC be used without a backend server?
A: No. While the media is peer-to-peer, a backend is needed for signaling (e.g., exchanging offers and answers) and authentication. - How does ICE help in WebRTC?
A: ICE (Interactive Connectivity Establishment) tries different network paths (using STUN/TURN) to find the best way to connect two peers. - What are RTCDataChannels?
A: RTCDataChannels are part of WebRTC and allow direct, peer-to-peer data transfer — useful for file sharing, chat messages, or real-time syncing. - How does WebRTC integrate with a Rails app?
A: WebRTC handles media on the frontend. Rails (with ActionCable) can act as the signaling server to help browsers exchange offers, answers, and ICE candidates. - What are the common challenges in WebRTC development?
A: Handling NAT/firewalls, ensuring compatibility across browsers, securing signaling and media access, and setting up TURN/STUN servers are common challenges.
Tools and Libraries for WebRTC
WebRTC is built into modern browsers, but for real-world apps — especially with Rails — you’ll need extra tools to handle signaling, UI, and server-side logic. Here’s a list of essential tools and libraries:
🧰 Core Tools
- WebRTC API (Browser): Built-in API used via JavaScript. It includes:
getUserMedia()
– Access camera and microphoneRTCPeerConnection
– Connect users directlyRTCDataChannel
– Share data between peers
- STUN/TURN Servers: Required for NAT traversal and relay. Examples:
- Coturn – Free, open-source TURN/STUN server
- Google STUN Server –
stun:stun.l.google.com:19302
🔧 JavaScript Libraries
- SimpleWebRTC: Easiest way to get started with WebRTC calls. Great for beginners.
GitHub Repo - PeerJS: Simplifies WebRTC peer connections with fallback support.
https://peerjs.com - Adapter.js: A shim library to ensure WebRTC works across different browsers consistently.
GitHub Repo
🧠 Rails-Specific Tools
- ActionCable (Rails 5+): Built-in WebSocket system in Rails used for signaling between peers.
- Redis: Optional, but often used to scale ActionCable (Pub/Sub backend).
- Devise: Used to manage user sessions and authentication before allowing access to calls.
🖥️ UI Tools (Optional)
- Bootstrap/Tailwind CSS: To style video call interfaces with responsive layouts.
- FontAwesome: For adding call buttons, mute, hang-up, and mic icons.
Using these tools together, you can build a fully functional, secure, and scalable video calling app with WebRTC inside a Ruby on Rails project.
Performance Tips and Best Practices for WebRTC
WebRTC is powerful, but real-time communication can be resource-heavy and unpredictable across networks. Here are proven performance tips and best practices to keep your WebRTC + Rails app smooth, secure, and scalable.
📈 Performance Optimization Tips
- Use TURN servers only when needed: TURN relays are slower and costly. Prefer STUN for faster direct connections.
- Limit media resolution: Use
{ video: { width: 640, height: 480 } }
to reduce bandwidth and CPU load unless HD is required. - Handle network changes: Listen to
iceconnectionstatechange
and reconnect if needed (mobile users switch Wi-Fi/cellular). - Disable unnecessary tracks: Stop or mute unused video/audio tracks to save CPU and bandwidth.
- Use MediaRecorder wisely: Recording streams can spike CPU. Record only when necessary, and compress offline if possible.
🔐 Security Best Practices
- Enforce HTTPS: WebRTC will not run without it (except on localhost).
- Secure signaling (ActionCable): Authenticate users before letting them join or broadcast in rooms.
- Use short-lived tokens for rooms: Don’t use predictable IDs — generate UUIDs or signed room tokens.
- Restrict media access: Only request camera/mic when needed and show clear visual cues when streaming starts.
⚙️ Server & App-Level Practices
- Use Redis for ActionCable: Helps scale your signaling layer in production.
- Broadcast efficiently: Don’t rebroadcast full stream data — WebRTC handles that. Use ActionCable only for signaling.
- Track connection states: Log connection start, ICE failures, disconnections for debugging and analytics.
- Clean up after disconnects: Remove stale peers or close tracks when users leave the room.
🎨 UX Best Practices
- Show connection status: Display “connecting…”, “waiting…”, “connected”, and error states.
- Let users control devices: Add buttons to mute audio, turn off video, or switch camera (mobile).
- Fallback notice: If the user’s browser doesn’t support WebRTC, show a message or offer to download an app.
By following these best practices, your WebRTC application built with Rails will run smoother, consume fewer resources, and provide a better experience for users — even across different networks and devices.
Alternatives to WebRTC
Technology | Pros | Cons |
---|---|---|
Zoom SDK | Enterprise support, stable | Expensive, less customizable |
Twilio Video | Quick to integrate, good docs | Paid service |
Agora | Global infrastructure, great APIs | Requires API key, cost involved |
Daily.co | WebRTC-based, easy | Usage-limited on free plan |
Real World Case Study: Telehealth App
Problem: A clinic needed a secure, real-time video consultation system embedded in their existing Rails app.
Solution: WebRTC was used for peer-to-peer video, and ActionCable handled signaling. The server ensured authentication before allowing access to call rooms.
Outcome: Reduced wait times and remote patient care, no additional licensing costs for third-party video SDKs.
🚀 Complete WebRTC Implementation Tutorial
This comprehensive tutorial walks through the actual implementation of one-to-one video calls in a Rails app using WebRTC and Action Cable. Every code snippet comes from a working project.
📋 What We’re Building
A Rails app where users can:
- See who’s online in a WhatsApp-style user list
- Click to start video calls with other users
- Accept/decline incoming calls
- Have real-time video/audio conversations
🛠️ Tech Stack
- Rails 7.1.3 + PostgreSQL
- Devise for authentication
- Action Cable for real-time signaling
- WebRTC for peer-to-peer video/audio
- Stimulus for frontend logic
📊 Project Setup
Step 1: Create Call Model
# Generate the Call model with all necessary fields
rails generate model Call caller:references callee:references status:string call_type:string room:string
rails db:migrate
Step 2: Add Online Status to Users
# Add online status to track which users are currently active
rails generate migration AddOnlineToUsers online:boolean
rails db:migrate
🏗️ Models & Associations
User Model (app/models/user.rb)
class User < ApplicationRecord
# Devise authentication modules
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
# Call associations - a user can be either caller or callee
has_many :outgoing_calls, class_name: 'Call', foreign_key: :caller_id
has_many :incoming_calls, class_name: 'Call', foreign_key: :callee_id
end
Call Model (app/models/call.rb)
class Call < ApplicationRecord
belongs_to :caller, class_name: 'User'
belongs_to :callee, class_name: 'User'
validates :status, presence: true
validates :call_type, presence: true
validates :room, presence: true, uniqueness: true
end
📡 Action Cable Channels
UserChannel (app/channels/user_channel.rb)
class UserChannel < ApplicationCable::Channel
def subscribed
stream_from "user_#{current_user.id}"
end
end
CallChannel (app/channels/call_channel.rb)
class CallChannel < ApplicationCable::Channel
def subscribed
stream_from "call_#{params[:room]}"
end
def receive(data)
ActionCable.server.broadcast("call_#{params[:room]}", data)
end
end
🎮 Controllers & Routes
Routes (config/routes.rb)
Rails.application.routes.draw do
devise_for :users
get 'dashboard', to: 'dashboard#index'
resources :calls, only: [:create] do
member do
get :room
post :accept
post :decline
end
end
end
Calls Controller (app/controllers/calls_controller.rb)
class CallsController < ApplicationController
before_action :authenticate_user!
def create
callee = User.find(params[:user_id])
call = Call.create!(
caller: current_user,
callee: callee,
status: "pending",
call_type: params[:type],
room: SecureRandom.uuid
)
ActionCable.server.broadcast("user_#{callee.id}", {
type: "incoming_call",
call_id: call.id,
caller_name: current_user.email,
call_type: call.call_type,
room: call.room
})
render json: { call_id: call.id, room: call.room }, status: :created
end
def accept
call = Call.find(params[:id])
call.update!(status: "accepted")
[call.caller_id, call.callee_id].each do |user_id|
ActionCable.server.broadcast("user_#{user_id}", {
type: "call_accepted",
call_id: call.id,
room: call.room
})
end
head :ok
end
def room
@call = Call.find(params[:id])
unless [@call.caller_id, @call.callee_id].include?(current_user.id)
redirect_to dashboard_path, alert: 'You are not a participant in this call.'
return
end
end
end
🎯 Frontend: Stimulus Controllers
User Call Controller (app/javascript/controllers/user_call_controller.js)
import { Controller } from "@hotwired/stimulus"
import consumer from "channels/consumer"
export default class extends Controller {
connect() {
this.currentUserId = this.element.dataset.currentUserId
this.subscribeToUserChannel()
this.setupCallButtons()
}
subscribeToUserChannel() {
this.userChannel = consumer.subscriptions.create(
{ channel: "UserChannel" },
{
received: (data) => this.handleUserNotification(data)
}
)
}
handleUserNotification(data) {
if (data.type === "incoming_call") {
this.showIncomingCallModal(data)
} else if (data.type === "call_accepted") {
window.location.href = `/calls/${data.call_id}/room`
}
}
startCallRequest(userId, type) {
fetch('/calls', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ user_id: userId, type: type })
})
.then(res => res.json())
.then(data => {
this.pendingCall = data
this.showWaitingModal()
})
}
}
🔗 WebRTC Integration
Call Controller (app/javascript/controllers/call_controller.js)
import { Controller } from "@hotwired/stimulus"
import consumer from "channels/consumer"
export default class extends Controller {
static targets = ["localVideo", "remoteVideo", "endCallBtn"]
connect() {
this.isCaller = localStorage.getItem('isCaller') === 'true'
localStorage.removeItem('isCaller')
const pathMatch = window.location.pathname.match(/\/calls\/(\d+)\/room/)
if (pathMatch) {
this.room = pathMatch[1]
this.startWebRTCCall(this.room)
}
}
async startWebRTCCall(room) {
this.room = room
this.localStream = null
this.remoteStream = null
this.peerConnection = null
this.signaling = null
this.setupSignaling()
await this.startLocalStream()
if (this.isCaller) {
this.createOffer()
}
}
setupSignaling() {
this.signaling = consumer.subscriptions.create(
{ channel: "CallChannel", room: this.room },
{
received: (data) => this.handleSignalingData(data)
}
)
}
async startLocalStream() {
this.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
this.localVideoTarget.srcObject = this.localStream
}
createPeerConnection() {
this.peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.l.google.com:19302" }
]
})
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream)
})
this.peerConnection.ontrack = (event) => {
if (!this.remoteStream) {
this.remoteStream = new MediaStream()
this.remoteVideoTarget.srcObject = this.remoteStream
}
event.streams[0].getTracks().forEach(track => {
this.remoteStream.addTrack(track)
})
}
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.sendSignal({ type: "candidate", candidate: event.candidate })
}
}
}
async createOffer() {
this.createPeerConnection()
const offer = await this.peerConnection.createOffer()
await this.peerConnection.setLocalDescription(offer)
this.sendSignal({ type: "offer", sdp: offer })
}
async handleSignalingData(data) {
if (data.type === "offer" && !this.isCaller) {
this.createPeerConnection()
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp))
const answer = await this.peerConnection.createAnswer()
await this.peerConnection.setLocalDescription(answer)
this.sendSignal({ type: "answer", sdp: answer })
} else if (data.type === "answer" && this.isCaller) {
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp))
} else if (data.type === "candidate") {
if (this.peerConnection && this.peerConnection.remoteDescription) {
await this.peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate))
}
}
}
sendSignal(data) {
if (this.signaling) {
this.signaling.send(data)
}
}
endCall() {
this.sendSignal({ type: "call_ended" })
this.endCallCleanup(true)
}
endCallCleanup(redirect = true) {
if (this.peerConnection) {
this.peerConnection.close()
this.peerConnection = null
}
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop())
this.localStream = null
}
if (redirect) {
window.location.href = "/dashboard"
}
}
}
🎨 UI Implementation
Dashboard View (app/views/dashboard/index.html.erb)
<div class="container py-5" data-controller="user-call" data-current-user-id="<%= current_user.id %>">
<div class="user-list-panel">
<div class="user-list-header">Chats</div>
<div class="user-list-scroll">
<% if @online_users.any? %>
<div class="user-list-group-label">Online</div>
<% @online_users.each do |user| %>
<div class="user-list-row" data-user-id="<%= user.id %>">
<%= image_tag('https://ui-avatars.com/api/?name=' + URI.encode_www_form_component(user.email), class: "user-list-avatar") %>
<span class="user-list-email"><%= user.email %></span>
<div class="user-list-actions">
<%= button_to "Video", calls_path(type: 'video', user_id: user.id), method: :post, class: "user-list-call-btn" %>
<%= button_to "Audio", calls_path(type: 'audio', user_id: user.id), method: :post, class: "user-list-call-btn" %>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
Call Room View (app/views/calls/room.html.erb)
<div class="call-room-container" data-controller="call" data-turbo="false">
<div id="callStatus" class="call-status">Connecting...</div>
<div class="call-videos">
<video id="remoteVideo" class="call-video-main" autoplay playsinline data-call-target="remoteVideo"></video>
<video id="localVideo" class="call-video-small" autoplay playsinline muted data-call-target="localVideo"></video>
</div>
<div class="call-controls">
<button id="endCallBtn" class="btn btn-danger btn-lg" data-action="click->call#endCall">
End Call
</button>
</div>
</div>
🔄 Complete Flow Walkthrough
Step-by-Step Call Process:
- User A clicks “Video Call” on User B
user_call_controller.js
sends POST to/calls
CallsController#create
creates Call record- Action Cable broadcasts to User B’s channel
- User B receives incoming call
UserChannel
receives notification- Modal appears with accept/decline buttons
- User B accepts call
- POST to
/calls/:id/accept
- Both users get “call_accepted” notification
- Both redirect to
/calls/:id/room
- POST to
- WebRTC Connection Setup
- Both users join
CallChannel
with room ID - Caller creates offer, sends via Action Cable
- Callee receives offer, creates answer, sends back
- Both exchange ICE candidates
- Direct peer-to-peer connection established
- Both users join
- Video/Audio Streams
- Local streams attached to video elements
- Remote streams received via
ontrack
event - Real-time video/audio communication
All code shown is from the actual working project and can be used as a reference for building similar features!
📚 External Resources & References
Here are additional resources, demo projects, and references to help you learn more about WebRTC and implement it in your Rails applications.
🚀 Demo Projects & Examples
- WebRTC Calls Demo Project
A complete Rails application demonstrating WebRTC video calls with Action Cable signaling. Features user authentication, real-time call management, and peer-to-peer video communication. - SimpleWebRTC
The easiest way to get started with WebRTC calls. Great for beginners and prototyping. - WebRTC Samples
Official WebRTC samples and demos from the WebRTC team, covering various use cases and implementations.
📖 Official Documentation
- MDN WebRTC API Documentation
Comprehensive documentation covering all WebRTC APIs, concepts, and implementation examples. - WebRTC.org Getting Started
Official WebRTC project documentation with tutorials and best practices. - Rails Action Cable Guide
Official Rails documentation for Action Cable WebSocket implementation.
🛠️ Development Tools & Libraries
- PeerJS
Simplifies WebRTC peer connections with fallback support and easy-to-use API. - WebRTC Adapter
A shim library to ensure WebRTC works across different browsers consistently. - Coturn TURN Server
Free, open-source TURN/STUN server for NAT traversal and media relay. - mediasoup
Advanced WebRTC SFU (Selective Forwarding Unit) for scalable video conferencing.
🎓 Learning Resources
- HTML5 Rocks WebRTC Tutorial
Excellent tutorial covering WebRTC basics and implementation patterns.
Conclusion
WebRTC brings powerful, real-time communication to Rails applications. Combined with ActionCable, it enables fully custom video and audio experiences without external services. Perfect for modern apps needing privacy and full-stack control.
Learn more about Rails
Refer friends, earn cash—sign up now! https://shorturl.fm/vSgFl
Get paid for every click—join our affiliate network now! https://shorturl.fm/wkOvk
Promote our brand, reap the rewards—apply to our affiliate program today! https://shorturl.fm/vU47N
Unlock exclusive rewards with every referral—enroll now! https://shorturl.fm/3A6jo
Join our affiliate program and watch your earnings skyrocket—sign up now! https://shorturl.fm/DUMqf
Earn passive income this month—become an affiliate partner and get paid! https://shorturl.fm/4BRcc
Turn traffic into cash—apply to our affiliate program today! https://shorturl.fm/Oe9Ft
Earn up to 40% commission per sale—join our affiliate program now! https://shorturl.fm/Cf9UA
Start profiting from your traffic—sign up today! https://shorturl.fm/rN5bd
Join our affiliate program today and earn generous commissions! https://shorturl.fm/oDz4h
Maximize your income with our high-converting offers—join as an affiliate! https://shorturl.fm/57oIL
Boost your income—enroll in our affiliate program today! https://shorturl.fm/34iW4
Apply now and unlock exclusive affiliate rewards! https://shorturl.fm/xo0cE
Start earning passive income—become our affiliate partner! https://shorturl.fm/RC4uN
Monetize your audience—become an affiliate partner now! https://shorturl.fm/3cENA
Join our affiliate program today and start earning up to 30% commission—sign up now! https://shorturl.fm/SROGB
Start earning on every sale—become our affiliate partner today! https://shorturl.fm/Gv70g
Share your unique link and earn up to 40% commission! https://shorturl.fm/85xdL
Share our products, earn up to 40% per sale—apply today! https://shorturl.fm/t3GKX
Boost your income—enroll in our affiliate program today! https://shorturl.fm/L2va1
Unlock top-tier commissions—become our affiliate partner now! https://shorturl.fm/GTbuh
Be rewarded for every click—join our affiliate program today! https://shorturl.fm/rcq1c
Become our partner and turn clicks into cash—join the affiliate program today! https://shorturl.fm/JWkCI
Monetize your influence—become an affiliate today! https://shorturl.fm/63ttv
Get paid for every click—join our affiliate network now! https://shorturl.fm/xjblH
Your audience, your profits—become an affiliate today! https://shorturl.fm/MiGC0
Become our affiliate and watch your wallet grow—apply now! https://shorturl.fm/N68W4
Promote our products and earn real money—apply today! https://shorturl.fm/wpQ1E
Become our affiliate—tap into unlimited earning potential! https://shorturl.fm/IgwZB
Share our products, earn up to 40% per sale—apply today! https://shorturl.fm/Ipxy4
Earn passive income with every click—sign up today! https://shorturl.fm/kensu
Monetize your audience with our high-converting offers—apply today! https://shorturl.fm/kSsFU
Get started instantly—earn on every referral you make! https://shorturl.fm/h8pWN
Join our affiliate community and earn more—register now! https://shorturl.fm/MQ80E
Boost your profits with our affiliate program—apply today! https://shorturl.fm/v38uM
Your audience, your profits—become an affiliate today! https://shorturl.fm/WFMXa
Share our offers and watch your wallet grow—become an affiliate! https://shorturl.fm/mVKGO
Start profiting from your network—sign up today! https://shorturl.fm/dh0SM
Share your unique link and cash in—join now! https://shorturl.fm/8Ksz7
Partner with us and enjoy recurring commission payouts! https://shorturl.fm/YebfT
Share your link, earn rewards—sign up for our affiliate program! https://shorturl.fm/CDpaH
Unlock exclusive rewards with every referral—apply to our affiliate program now! https://shorturl.fm/vdsQL
https://shorturl.fm/GPJV8
https://shorturl.fm/x6tMc
https://shorturl.fm/8rAQd