set up action able to work with webrtc in localhost
This commit is contained in:
parent
f9cbb7c680
commit
aa3693d58c
|
@ -0,0 +1,196 @@
|
||||||
|
// Broadcast Types
|
||||||
|
|
||||||
|
class Signaling {}
|
||||||
|
|
||||||
|
const JOIN_ROOM = "JOIN_ROOM";
|
||||||
|
const EXCHANGE = "EXCHANGE";
|
||||||
|
const REMOVE_USER = "REMOVE_USER";
|
||||||
|
|
||||||
|
// DOM Elements
|
||||||
|
let currentUser;
|
||||||
|
let localVideo;
|
||||||
|
let remoteVideoContainer;
|
||||||
|
|
||||||
|
// Objects
|
||||||
|
let pcPeers = {}; // peer connection
|
||||||
|
let localstream;
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
currentUser = document.getElementById("current-user").innerHTML;
|
||||||
|
localVideo = document.getElementById("local-video");
|
||||||
|
remoteVideoContainer = document.getElementById("remote-video-container");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ice Credentials
|
||||||
|
const ice = { iceServers: [{urls: ['stun:stun.l.google.com:19302', 'stun:stun.1.google.com:19302']}]};
|
||||||
|
|
||||||
|
// Initialize user's own video
|
||||||
|
document.onreadystatechange = async () => {
|
||||||
|
if (document.readyState === "interactive") {
|
||||||
|
try {
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: true,
|
||||||
|
video: true
|
||||||
|
})
|
||||||
|
|
||||||
|
localstream = stream;
|
||||||
|
localVideo.srcObject = stream
|
||||||
|
localVideo.muted = true
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleJoinSession = async () => {
|
||||||
|
App.session = await App.cable.subscriptions.create("VideoSessionChannel", {
|
||||||
|
connected: () => {
|
||||||
|
broadcastData({
|
||||||
|
type: JOIN_ROOM,
|
||||||
|
from: currentUser
|
||||||
|
});
|
||||||
|
},
|
||||||
|
received: data => {
|
||||||
|
console.log("received", data);
|
||||||
|
if (data.from === currentUser) return;
|
||||||
|
switch (data.type) {
|
||||||
|
case JOIN_ROOM:
|
||||||
|
return joinRoom(data);
|
||||||
|
case EXCHANGE:
|
||||||
|
if (data.to !== currentUser) return;
|
||||||
|
return exchange(data);
|
||||||
|
case REMOVE_USER:
|
||||||
|
return removeUser(data);
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLeaveSession = () => {
|
||||||
|
for (user in pcPeers) {
|
||||||
|
pcPeers[user].close();
|
||||||
|
}
|
||||||
|
pcPeers = {};
|
||||||
|
|
||||||
|
App.session.unsubscribe();
|
||||||
|
|
||||||
|
remoteVideoContainer.innerHTML = "";
|
||||||
|
|
||||||
|
broadcastData({
|
||||||
|
type: REMOVE_USER,
|
||||||
|
from: currentUser
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const joinRoom = data => {
|
||||||
|
createPC(data.from, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeUser = data => {
|
||||||
|
console.log("removing user", data.from);
|
||||||
|
let video = document.getElementById(`remoteVideoContainer+${data.from}`);
|
||||||
|
video && video.remove();
|
||||||
|
delete pcPeers[data.from];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const broadcastData = data => {
|
||||||
|
fetch("sessions", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: { "content-type": "application/json" }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPC = (userId, isOffer) => {
|
||||||
|
let pc = new RTCPeerConnection(ice);
|
||||||
|
pcPeers[userId] = pc;
|
||||||
|
pc.addStream(localstream);
|
||||||
|
|
||||||
|
if (isOffer) {
|
||||||
|
pc
|
||||||
|
.createOffer()
|
||||||
|
.then(offer => {
|
||||||
|
pc.setLocalDescription(offer);
|
||||||
|
broadcastData({
|
||||||
|
type: EXCHANGE,
|
||||||
|
from: currentUser,
|
||||||
|
to: userId,
|
||||||
|
sdp: JSON.stringify(pc.localDescription)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(logError);
|
||||||
|
}
|
||||||
|
|
||||||
|
pc.onicecandidate = event => {
|
||||||
|
if (event.candidate) {
|
||||||
|
broadcastData({
|
||||||
|
type: EXCHANGE,
|
||||||
|
from: currentUser,
|
||||||
|
to: userId,
|
||||||
|
candidate: JSON.stringify(event.candidate)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pc.onaddstream = event => {
|
||||||
|
const element = document.createElement("video");
|
||||||
|
element.id = `remoteVideoContainer+${userId}`; // why is the userId being interpolated?
|
||||||
|
element.autoplay = "autoplay";
|
||||||
|
element.srcObject = event.stream;
|
||||||
|
remoteVideoContainer.appendChild(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
pc.oniceconnectionstatechange = event => {
|
||||||
|
if (pc.iceConnectionState == "disconnected") {
|
||||||
|
console.log("Disconnected:", userId);
|
||||||
|
broadcastData({
|
||||||
|
type: REMOVE_USER,
|
||||||
|
from: userId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return pc;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const exchange = data => {
|
||||||
|
let pc;
|
||||||
|
|
||||||
|
if (!pcPeers[data.from]) {
|
||||||
|
pc = createPC(data.from, false);
|
||||||
|
} else {
|
||||||
|
pc = pcPeers[data.from];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.candidate) {
|
||||||
|
pc
|
||||||
|
.addIceCandidate(new RTCIceCandidate(JSON.parse(data.candidate)))
|
||||||
|
.then(() => console.log("Ice candidate added"))
|
||||||
|
.catch(logError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.sdp) {
|
||||||
|
sdp = JSON.parse(data.sdp);
|
||||||
|
pc
|
||||||
|
.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||||
|
.then(() => {
|
||||||
|
if (sdp.type === "offer") {
|
||||||
|
pc.createAnswer().then(answer => {
|
||||||
|
pc.setLocalDescription(answer);
|
||||||
|
broadcastData({
|
||||||
|
type: EXCHANGE,
|
||||||
|
from: currentUser,
|
||||||
|
to: data.from,
|
||||||
|
sdp: JSON.stringify(pc.localDescription)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(logError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const logError = error => console.warn("Whoops! Error:", error);
|
|
@ -1 +1,6 @@
|
||||||
// Specific CSS for your home-page
|
// Specific CSS for your home-page
|
||||||
|
video {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
-webkit-transform:rotateY(180deg); /* Safari and Chrome */
|
||||||
|
-moz-transform:rotateY(180deg); /* Firefox */
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
class VideoSessionChannel < ApplicationCable::Channel
|
class VideoSessionChannel < ApplicationCable::Channel
|
||||||
def subscribed
|
def subscribed
|
||||||
# video session
|
# video session
|
||||||
|
stream_from "video_session_channel"
|
||||||
# stream_from "chat_room_#{params[:chat_room_id]}"
|
# stream_from "chat_room_#{params[:chat_room_id]}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
protect_from_forgery with: :exception
|
protect_from_forgery with: :exception
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
protect_from_forgery unless: -> { request.format.json? } # Only accept json
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
class VideoSessionsController < ApplicationController
|
||||||
|
def create
|
||||||
|
# HTTP status code 200 with an empty body
|
||||||
|
head :no_content
|
||||||
|
ActionCable.server.broadcast "video_session_channel", session_params
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def session_params
|
||||||
|
# SDP = Session description protocol (codec info from client)
|
||||||
|
# Candidate = ICE candidates (e.g. TURN and STUN server)
|
||||||
|
params.permit(:type, :from, :to, :sdp, :candidate)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,2 +1,18 @@
|
||||||
<h1>Pages#home</h1>
|
<h1>Action Cable Signaling Server</h1>
|
||||||
<p>Find me in app/views/pages/home.html.erb</p>
|
|
||||||
|
<div>Random User ID:
|
||||||
|
<span id="current-user"><%= rand(0..10000) %></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="remote-video-container"></div>
|
||||||
|
<video id="local-video" autoplay></video>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<button onclick="handleJoinSession()">
|
||||||
|
Join Room
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onclick="handleLeaveSession()">
|
||||||
|
Leave Room
|
||||||
|
</button>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
<h1>VideoSessions#create</h1>
|
||||||
|
<p>Find me in app/views/video_sessions/create.html.erb</p>
|
|
@ -1,5 +1,10 @@
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
|
get 'video_sessions/create'
|
||||||
devise_for :users
|
devise_for :users
|
||||||
root to: 'pages#home'
|
root to: 'pages#home'
|
||||||
|
|
||||||
|
post '/sessions', to: 'video_sessions#create'
|
||||||
|
|
||||||
|
mount ActionCable.server, at: '/cable'
|
||||||
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
|
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class VideoSessionsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
test "should get create" do
|
||||||
|
get video_sessions_create_url
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue