Merge pull request #2 from beatriceo/action-cable

set up action able to work with webrtc in localhost
This commit is contained in:
Beatrice Olivera 2018-08-27 19:08:07 +01:00 committed by GitHub
commit 919019e939
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 252 additions and 2 deletions

View File

@ -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);

View File

@ -1 +1,6 @@
// Specific CSS for your home-page
video {
transform: rotateY(180deg);
-webkit-transform:rotateY(180deg); /* Safari and Chrome */
-moz-transform:rotateY(180deg); /* Firefox */
}

View File

@ -1,6 +1,7 @@
class VideoSessionChannel < ApplicationCable::Channel
def subscribed
# video session
stream_from "video_session_channel"
# stream_from "chat_room_#{params[:chat_room_id]}"
end

View File

@ -1,4 +1,5 @@
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!
protect_from_forgery unless: -> { request.format.json? } # Only accept json
end

View File

@ -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

View File

@ -1,2 +1,18 @@
<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>
<h1>Action Cable Signaling Server</h1>
<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>

View File

@ -0,0 +1,2 @@
<h1>VideoSessions#create</h1>
<p>Find me in app/views/video_sessions/create.html.erb</p>

View File

@ -1,5 +1,10 @@
Rails.application.routes.draw do
get 'video_sessions/create'
devise_for :users
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
end

View File

@ -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