[deploy] Connect Video Chat Experiment (#7099)

This commit is contained in:
Ben Halpern 2020-04-06 12:47:14 -04:00 committed by GitHub
parent 5c17e5c25d
commit e171abf916
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 0 deletions

View file

@ -0,0 +1,23 @@
class VideoChatsController < ApplicationController
before_action :authenticate_user!
def show
account_sid = ApplicationConfig["TWILIO_ACCOUNT_SID"]
api_key = ApplicationConfig["TWILIO_VIDEO_API_KEY"]
api_secret = ApplicationConfig["TWILIO_VIDEO_API_SECRET"]
token = Twilio::JWT::AccessToken.new(
account_sid,
api_key,
api_secret,
[],
identity: current_user.id,
)
grant = Twilio::JWT::AccessToken::VideoGrant.new
grant.room = params[:id]
token.add_grant(grant)
@token = token.to_jwt
end
end

View file

@ -0,0 +1,119 @@
import { h, Component, render } from 'preact';
import PropTypes from 'prop-types';
const root = document.getElementById('chat')
export default class VideoChat extends Component {
componentDidMount() {
this.setupCallChannel(root.dataset.token)
}
setupCallChannel = token => {
const component = this;
const activeChannelId = root.dataset.channel
console.log()
import('twilio-video').then(({ connect, createLocalVideoTrack }) => {
connect(
token,
{
name: `private-video-channel-${activeChannelId}`,
audio: true,
type: 'peer-to-peer',
video: { width: 640 },
},
).then(
function onConnectSuccess(room) {
console.log(room)
component.setState({ token: token, room });
createLocalVideoTrack().then(track => {
const localMediaContainer = document.getElementById(
'videolocalscreen',
);
localMediaContainer.appendChild(track.attach());
});
const roomParticipants = [];
room.participants.forEach(participant => {
component.triggerRemoteJoin(participant);
roomParticipants.push(participant);
});
component.setState({ participants: roomParticipants });
room.on('participantConnected', function onParticipantConnected(
participant,
) {
console.log('participant joined')
component.triggerRemoteJoin(participant);
room.participants.forEach(p => {
roomParticipants.push(p);
});
component.setState({ participants: roomParticipants });
room.on(
'participantDisconnected',
function onParticipantDisconnected() {
console.log('disconnected')
},
);
participant.on('dominantSpeakerChanged', dominantSpeaker => {
// eslint-disable-next-line no-console
console.log(
'The new dominant speaker in the Room is:',
dominantSpeaker,
);
});
});
},
function onConnectFailure(error) {
document.getElementById('videoremotescreen').innerHTML = '';
// eslint-disable-next-line no-console
console.error(`Unable to connect to Room: ${error.message}`);
},
);
});
};
triggerRemoteJoin = participant => {
participant.on('trackSubscribed', track => {
console.log(track)
if (!document.getElementById(`${track.kind}-${track.id}`)) {
const trackDiv = document.createElement('div');
trackDiv.className = `chat__videocalltrackdiv--${track.kind}`;
trackDiv.id = participant.sid;
trackDiv.appendChild(track.attach());
document.getElementById('videoremotescreen').appendChild(trackDiv);
document.getElementById(
'videoremotescreen',
).lastChild.id = `${track.kind}-${track.sid}`;
}
});
participant.on('trackRemoved', track => {
if (document.getElementById(track.id)) {
document.getElementById(track.id).outerHTML = '';
}
});
// participant.on('trackDisabled', track => {
// console.log('disabled')
// console.log('TODO: Show track status on video')
// console.log(track.mediaStreamTrack.id)
// });
// participant.on('trackEnabled', track => {
// console.log('enabled')
// console.log('TODO: Show track status on video')
// console.log(track.mediaStreamTrack.id)
// });
};
render() {
return <div>
<div id="videoremotescreen"></div>
<div id="videolocalscreen"></div>
</div>
}
}
render(<VideoChat />, root, root.firstElementChild);

View file

@ -118,6 +118,14 @@ class Message < ApplicationRecord
#{user.name}
</h1>
</a>".html_safe
elsif anchor["href"].include?("/video_chats/")
html += "<a href='#{anchor['href']}'
class='chatchannels__richlink'
target='_blank' data-content='sidecar-video'>
<h1 data-content='sidecar-video'>
👾 Experimental Video Chat Started
</h1>
</a>".html_safe
end
end
html

View file

@ -0,0 +1,13 @@
<div class="video-chat-wrapper" id="chat" data-token="<%= @token %>" data-channel="<%= params[:id] %>">
</div>
<h1>This is an experiment</h1>
<h3>It's only a partially implemented view.</h3>
<h4>But this could be a more straightforward way to do video chat within /connect.</h4>
<%# TODO Make paramsp[:id] map to a chat channel and ensure the users have permission %>
<%# That part is pretty straightforward %>
<%# Then close the loop on the JavaScript part of it. %>
<%# Also should be *fairly* straightforward in terms of following a basic tutorial. %>
<%# Then we drop a link in chat rather than trying to make video work with complicated state in chat. %>
<%# The complicated part has always been chat state/channels etc. Doing in sidecar means more containerization of logic. %>
<%= javascript_packs_with_chunks_tag "videoChat", defer: true %>

View file

@ -214,6 +214,7 @@ Rails.application.routes.draw do
resources :user_blocks, param: :blocked_id, only: %i[show create destroy]
resources :podcasts, only: %i[new create]
resources :article_approvals, only: %i[create]
resources :video_chats, only: %i[show]
resolve("ProMembership") { [:pro_membership] } # see https://guides.rubyonrails.org/routing.html#using-resolve
namespace :followings, defaults: { format: :json } do
get :users

View file

@ -0,0 +1,25 @@
require "rails_helper"
RSpec.describe "VideoChats", type: :request do
let(:user) { create(:user) }
describe "GET /video_chats/:id" do
context "with user signed in" do
before do
sign_in user
end
it "displays basic html for working" do
get "/video_chats/1"
expect(response.body).to include("<div class=\"video-chat-wrapper")
end
end
context "without user signed in" do
it "asks to sign in" do
get "/video_chats/1"
expect(response).to redirect_to("/enter")
end
end
end
end