[deploy] Connect Video Chat Experiment (#7099)
This commit is contained in:
parent
5c17e5c25d
commit
e171abf916
6 changed files with 189 additions and 0 deletions
23
app/controllers/video_chats_controller.rb
Normal file
23
app/controllers/video_chats_controller.rb
Normal 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
|
||||
119
app/javascript/packs/videoChat.jsx
Normal file
119
app/javascript/packs/videoChat.jsx
Normal 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);
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
13
app/views/video_chats/show.html.erb
Normal file
13
app/views/video_chats/show.html.erb
Normal 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 %>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
25
spec/requests/video_chats_spec.rb
Normal file
25
spec/requests/video_chats_spec.rb
Normal 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
|
||||
Loading…
Add table
Reference in a new issue