[deploy] Remove old video code and clean up some functionality (#7217)

This commit is contained in:
Ben Halpern 2020-04-10 12:54:49 -04:00 committed by GitHub
parent ea1c0b85af
commit 5ce2152a5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 220 additions and 532 deletions

View file

@ -544,6 +544,50 @@
}
}
.chat--video-visible {
.activechatchannel__conversation {
min-width: 25%;
@media screen and (min-width: 1440px) {
min-width: 45%;
}
}
.activechatchannel__activecontent {
&.activechatchannel__activecontent--video {
width: 380px;
min-width: 380px;
@media screen and (max-width: 426px) {
width: calc(100% - 16px);
min-width: calc(100% - 16px);
}
iframe {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
width: 100%;
border: 0px;
}
}
width: 300px;
min-width: 300px;
@media screen and (min-width: 1000px) {
width: 400px;
min-width: 400px;
}
@media screen and (min-width: 1300px) {
width: 500px;
min-width: 500px;
}
}
}
.chat--content-visible .activechatchannel__activecontent.activechatchannel__activecontent--video {
width: 280px;
min-width: 280px;
}
.live-chat-wrapper .activechatchannel__activecontent {
min-width: 310px;
margin-left: -180px;

View file

@ -9,7 +9,8 @@ class VideoChatsController < ApplicationController
account_sid = ApplicationConfig["TWILIO_ACCOUNT_SID"]
api_key = ApplicationConfig["TWILIO_VIDEO_API_KEY"]
api_secret = ApplicationConfig["TWILIO_VIDEO_API_SECRET"]
@username = "@" + current_user.username
@username = display_username
@video_type = video_type
token = Twilio::JWT::AccessToken.new(
account_sid,
api_key,
@ -25,4 +26,20 @@ class VideoChatsController < ApplicationController
@username = @username
@token = token.to_jwt
end
private
def display_username
return "@#{params[:username]}" if params[:username] && Rails.env.development? #simpler solo testing in dev
"@" + current_user.username
end
def video_type
if @chat_channel.channel_type == "direct" || @chat_channel.chat_channel_memberships.size < 5
"peer-to-peer"
else
"group"
end
end
end

View file

@ -2,7 +2,7 @@
exports[`<Chat /> should load chat 1`] = `
<div
class="chat chat--expanded"
class="chat chat--expanded chat--video-not-visible chat--content-not-visible"
data-no-instant={true}
>
<div

View file

@ -1,46 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Video /> should render properly and test snapshot 1`] = `
<div
class="chat__videocall"
draggable="true"
id="chat__videocall"
onDrag={[Function]}
style={
Object {
"left": "170.66666666666666px",
"top": "76.8px",
}
}
>
<div
class="chat__remotevideoscreen-0"
id="videoremotescreen"
/>
<div
class="chat__localvideoscren"
id="videolocalscreen"
/>
<button
class="chat__videocallexitbutton"
onClick={[Function]}
type="button"
>
×
</button>
<button
class="chat__videocallcontrolbutton"
onClick={[Function]}
type="button"
>
UnMute
</button>
<button
class="chat__videocallcontrolbutton chat__videocallcontrolbutton--videoonoff"
onClick={[Function]}
type="button"
>
Turn On Video
</button>
</div>
`;

View file

@ -1,37 +0,0 @@
import { h } from 'preact';
import render from 'preact-render-to-json';
import { deep } from 'preact-render-spy';
import fetch from 'jest-fetch-mock';
import Video from '../video';
global.fetch = fetch;
let exited;
exited = false;
const exitVideo = () => {
exited = true;
};
describe('<Video />', () => {
it('should render properly and test snapshot', () => {
const tree = render(<Video activeChannelId={12345} onExit={exitVideo} />);
expect(tree).toMatchSnapshot();
});
it('should have the proper elements, classes and information', () => {
const context = deep(<Video activeChannelId={12345} onExit={exitVideo} />);
// check elements
expect(context.find('.chat__videocall').exists()).toEqual(true);
expect(context.find('.chat__remotevideoscreen-0').exists()).toEqual(true);
expect(context.find('.chat__localvideoscren').exists()).toEqual(true);
const exitButton = context.find('.chat__videocallexitbutton');
expect(exitButton.exists()).toEqual(true);
expect(exitButton.text()).toEqual('×');
// test exit button behaves
exitButton.simulate('click');
expect(exited).toEqual(true);
});
});

View file

@ -152,17 +152,6 @@ export function getUnopenedChannelIds(successCb) {
});
}
export function getTwilioToken(videoChannelName, successCb, failureCb) {
fetch(`/twilio_tokens/${videoChannelName}`, {
Accept: 'application/json',
'Content-Type': 'application/json',
credentials: 'same-origin',
})
.then(response => response.json())
.then(successCb)
.catch(failureCb);
}
export function getContent(url, successCb, failureCb) {
fetch(url, {
Accept: 'application/json',

View file

@ -11,16 +11,12 @@ const Channels = ({
expanded,
filterQuery,
channelsLoaded,
incomingVideoCallChannelIds,
}) => {
const channels = chatChannels.map(channel => {
const isActive = parseInt(activeChannelId, 10) === channel.chat_channel_id;
const isUnopened =
!isActive && unopenedChannelIds.includes(channel.chat_channel_id);
let newMessagesIndicator = isUnopened ? 'new' : 'old';
if (incomingVideoCallChannelIds.indexOf(channel.chat_channel_id) > -1) {
newMessagesIndicator = 'video';
}
const otherClassname = isActive
? 'chatchanneltab--active'
: 'chatchanneltab--inactive';
@ -122,7 +118,6 @@ Channels.propTypes = {
expanded: PropTypes.bool.isRequired,
filterQuery: PropTypes.string.isRequired,
channelsLoaded: PropTypes.bool.isRequired,
incomingVideoCallChannelIds: PropTypes.arrayOf(PropTypes.string).isRequired,
};
export default Channels;

View file

@ -21,7 +21,7 @@ import Compose from './compose';
import Message from './message';
import ActionMessage from './actionMessage';
import Content from './content';
import Video from './video';
import VideoContent from './videoContent'
import setupPusher from '../src/utils/pusher';
import debounceAction from '../src/utils/debounceAction';
@ -61,15 +61,12 @@ export default class Chat extends Component {
currentUserId: chatOptions.currentUserId,
notificationsPermission: null,
activeContent: {},
videoPath: null,
expanded: window.innerWidth > 600,
isMobileDevice: typeof window.orientation !== 'undefined',
subscribedPusherChannels: [],
activeVideoChannelId: null,
incomingVideoCallChannelIds: [],
videoCallParticipants: [],
inviteChannels: [],
soundOn: true,
videoOn: true,
messageOffset: 0,
showDeleteModal: false,
messageDeleteId: null,
@ -153,32 +150,6 @@ export default class Chat extends Component {
}
}
liveCoding = (e) => {
const { activeContent, activeChannelId } = this.state;
if (activeContent !== { type_of: 'code_editor' }) {
activeContent[activeChannelId] = { type_of: 'code_editor' };
this.setState({ activeContent });
}
if (document.querySelector('.CodeMirror')) {
const cm = document.querySelector('.CodeMirror').CodeMirror;
if (cm && e.context === 'initializing-live-code-channel') {
window.pusher.channel(e.channel).trigger('client-livecode', {
value: cm.getValue(),
cursorPos: cm.getCursor(),
});
} else if ((cm && e.keyPressed === true) || e.value.length > 0) {
const cursorCoords = e.cursorPos;
const cursorElement = document.createElement('span');
cursorElement.classList.add('cursorelement');
cursorElement.style.height = `${
cursorCoords.bottom - cursorCoords.top
}px`;
cm.setValue(e.value);
cm.setBookmark(e.cursorPos, { widget: cursorElement });
}
}
};
filterForActiveChannel = (channels, id) =>
channels.filter(
(channel) => channel.chat_channel_id === parseInt(id, 10),
@ -196,9 +167,6 @@ export default class Chat extends Component {
channelCleared: this.clearChannel,
redactUserMessages: this.redactUserMessages,
channelError: this.channelError,
liveCoding: this.liveCoding,
videoCallInitiated: this.receiveVideoCall,
videoCallEnded: this.receiveVideoCallHangup,
mentioned: this.mentioned,
messageOpened: this.messageOpened,
});
@ -479,19 +447,6 @@ export default class Chat extends Component {
}));
};
receiveVideoCall = (callObj) => {
this.setState((prevState) => ({
incomingVideoCallChannelIds: [...prevState, callObj.channelId],
}));
};
receiveVideoCallHangup = () => {
const { videoCallParticipants } = this.state;
if (videoCallParticipants.size < 1) {
this.setState({ activeVideoChannelId: null });
}
};
redactUserMessages = (res) => {
const { messages } = this.state;
const newMessages = hideMessages(messages, res.userId);
@ -606,10 +561,7 @@ export default class Chat extends Component {
message: '/call',
mentionedUsersId: this.getMentionedUsers(message),
};
this.setActiveContent({
path: `/video_chats/${activeChannelId}`,
type_of: 'article',
});
this.setState({videoPath: 'http://localhost:3000/video_chats/' + activeChannelId})
sendMessage(messageObject, this.handleSuccess, this.handleFailure);
} else if (message.startsWith('/new')) {
this.setActiveContentState(activeChannelId, {
@ -651,36 +603,6 @@ export default class Chat extends Component {
);
};
answerVideoCall = () => {
const { activeChannelId } = this.state;
this.setState({
activeVideoChannelId: activeChannelId,
incomingVideoCallChannelIds: [],
});
};
hangupVideoCall = () => {
const { activeVideoChannelId } = this.state;
window.pusher
.channel(`presence-channel-${activeVideoChannelId}`)
.trigger('client-endvideocall', {});
this.setState({
activeVideoChannelId: null,
});
};
handleVideoParticipantChange = (participants) => {
this.setState({ videoCallParticipants: participants });
};
toggleVideoSound = () => {
this.setState((prevState) => ({ soundOn: !prevState.soundOn }));
};
toggleVideoVideo = () => {
this.setState((prevState) => ({ videoOn: !prevState.videoOn }));
};
triggerSwitchChannel = (id, slug) => {
const {
chatChannels,
@ -812,6 +734,8 @@ export default class Chat extends Component {
path: `/chat_channel_memberships/${activeChannel.id}/edit`,
type_of: 'article',
});
} else if (content.startsWith('sidecar-video')) {
this.setState({videoPath: target.href || target.parentElement.href})
} else if (
content.startsWith('sidecar') ||
content.startsWith('article')
@ -1097,7 +1021,6 @@ export default class Chat extends Component {
channelsLoaded={state.channelsLoaded}
filterQuery={state.filterQuery}
expanded={state.expanded}
incomingVideoCallChannelIds={state.incomingVideoCallChannelIds}
/>
{notificationsState}
</div>
@ -1115,7 +1038,6 @@ export default class Chat extends Component {
{'>'}
</button>
<Channels
incomingVideoCallChannelIds={state.incomingVideoCallChannelIds}
activeChannelId={state.activeChannelId}
chatChannels={state.chatChannels}
unopenedChannelIds={state.unopenedChannelIds}
@ -1187,7 +1109,7 @@ export default class Chat extends Component {
.classList.remove('chatchanneljumpback__hide');
};
renderActiveChatChannel = (channelHeader, incomingCall) => {
renderActiveChatChannel = (channelHeader) => {
const { state, props } = this;
return (
@ -1203,7 +1125,6 @@ export default class Chat extends Component {
id="messagelist"
>
{this.renderMessages()}
{incomingCall}
<div className="messagelist__sentinel" id="messagelist__sentinel" />
</div>
<div
@ -1252,10 +1173,18 @@ export default class Chat extends Component {
pusherKey={props.pusherKey}
githubToken={props.githubToken}
/>
<VideoContent
videoPath={state.videoPath}
onExit ={this.triggerExitVideo}
/>
</div>
);
};
triggerExitVideo = () => {
this.setState({videoPath: null})
}
handleMention = (e) => {
const { activeChannel } = this.state;
const mention = e.keyCode === 64;
@ -1558,53 +1487,17 @@ export default class Chat extends Component {
</div>
);
}
let vid = '';
let incomingCall = '';
if (state.activeVideoChannelId) {
vid = (
<Video
activeChannelId={state.activeChannelId}
onToggleSound={this.toggleVideoSound}
onToggleVideo={this.toggleVideoVideo}
soundOn={state.soundOn}
videoOn={state.videoOn}
onExit={this.hangupVideoCall}
onParticipantChange={this.handleVideoParticipantChange}
/>
);
} else if (
state.incomingVideoCallChannelIds.includes(state.activeChannelId)
) {
incomingCall = (
<div
className="activechatchannel__incomingcall"
onClick={this.answerVideoCall}
onKeyUp={(e) => {
if (e.keyCode === 13) this.answerVideoCall();
}}
role="button"
tabIndex="0"
>
<span role="img" aria-label="waving">
👋
</span>
{' '}
Incoming Video Call
{' '}
</div>
);
}
return (
<div
className={`chat chat--${
state.expanded ? 'expanded' : 'contracted'
}${detectIOSSafariClass}`}
}${detectIOSSafariClass} chat--${ state.videoPath ? 'video-visible' : 'video-not-visible'
} chat--${state.activeContent[state.activeChannelId] ? 'content-visible' : 'content-not-visible'}`}
data-no-instant
>
{this.renderChatChannels()}
<div className="chat__activechat">
{vid}
{this.renderActiveChatChannel(channelHeader, incomingCall)}
{this.renderActiveChatChannel(channelHeader)}
</div>
</div>
);

View file

@ -1,244 +0,0 @@
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import { getTwilioToken } from './actions';
/**
* TODO: Instead of calling this function in render, use jsx (<VideoControlButton />).
*/
function VideoControlButton({ btnClassName, btnClickCallback, btnLabel }) {
return (
<button type="button" className={btnClassName} onClick={btnClickCallback}>
{btnLabel}
</button>
);
}
VideoControlButton.propTypes = {
btnClassName: PropTypes.string.isRequired,
btnClickCallback: PropTypes.func.isRequired,
btnLabel: PropTypes.string.isRequired,
};
export default class Video extends Component {
static propTypes = {
activeChannelId: PropTypes.string.isRequired,
onToggleSound: PropTypes.func.isRequired,
onToggleVideo: PropTypes.func.isRequired,
onExit: PropTypes.func.isRequired,
soundOn: PropTypes.bool.isRequired,
videoOn: PropTypes.bool.isRequired,
};
constructor(props) {
super(props);
let leftPx = 40;
let topPx = 70;
if (window.innerWidth > 1500) {
leftPx = window.innerWidth / 5 + 200;
topPx = window.innerHeight / 10;
} else if (window.innerWidth > 641) {
leftPx = window.innerWidth / 6;
topPx = window.innerHeight / 10;
}
this.state = {
leftPx,
topPx,
pageX: null,
pageY: null,
room: null,
participants: [],
};
}
componentDidMount() {
const { activeChannelId } = this.props;
getTwilioToken(
`private-video-channel-${activeChannelId}`,
this.setupCallChannel,
);
}
componentWillUnmount() {
const { room } = this.state;
room.disconnect();
}
setupCallChannel = response => {
const component = this;
const { activeChannelId } = this.props;
import('twilio-video').then(({ connect, createLocalVideoTrack }) => {
connect(
response.token,
{
name: `private-video-channel-${activeChannelId}`,
audio: true,
type: 'peer-to-peer',
video: { width: 640 },
},
).then(
function onConnectSuccess(room) {
component.setState({ token: response.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,
) {
component.props.onParticipantChange(room.participants);
component.triggerRemoteJoin(participant);
room.participants.forEach(p => {
roomParticipants.push(p);
});
component.setState({ participants: roomParticipants });
room.on(
'participantDisconnected',
function onParticipantDisconnected() {
component.props.onParticipantChange(room.participants);
},
);
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('trackAdded', track => {
if (!document.getElementById(`${track.kind}-${track.id}`)) {
const trackDiv = document.createElement('div');
trackDiv.className = `chat__videocalltrackdiv--${track.kind}`;
trackDiv.appendChild(track.attach());
document.getElementById('videoremotescreen').appendChild(trackDiv);
document.getElementById(
'videoremotescreen',
).lastChild.id = `${track.kind}-${track.id}`;
}
});
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)
// });
};
handleDrag = e => {
const { pageX, offsetDiffX, pageY, offsetDiffY } = this.state;
if (!pageX) {
this.setState({
pageX: e.pageX,
pageY: e.pageY,
offsetDiffX: e.pageX - e.target.offsetLeft,
offsetDiffY: e.pageY - e.target.offsetTop,
});
} else if (e.pageX !== 0) {
this.setState({
leftPx: pageX + e.pageX - pageX - offsetDiffX,
topPx: pageY + e.pageY - pageY - offsetDiffY,
});
} else if (e.pageX === 0) {
this.setState({
pageX: null,
pageY: null,
offsetDiffX: null,
offsetDiffY: null,
});
}
};
toggleSound = () => {
const { room } = this.state;
const { onToggleSound } = this.props;
if (room) {
room.localParticipant.audioTracks.forEach(track => {
if (track.isEnabled) {
track.disable();
} else {
track.enable();
}
});
}
onToggleSound();
};
toggleVideo = () => {
const { room } = this.state;
const { onToggleVideo } = this.props;
if (room) {
room.localParticipant.videoTracks.forEach(track => {
if (track.isEnabled) {
track.disable();
} else {
track.enable();
}
});
}
onToggleVideo();
};
render() {
const { topPx, leftPx, participants } = this.state;
const { onExit, soundOn, videoOn } = this.props;
return (
<div
className="chat__videocall"
id="chat__videocall"
draggable="true"
onDrag={this.handleDrag}
style={{ left: `${leftPx}px`, top: `${topPx}px` }}
>
<div
id="videoremotescreen"
className={`chat__remotevideoscreen-${participants.length}`}
/>
<div className="chat__localvideoscren" id="videolocalscreen" />
{VideoControlButton({
btnClassName: 'chat__videocallexitbutton',
btnClickCallback: onExit,
btnLabel: '×',
})}
{VideoControlButton({
btnClassName: 'chat__videocallcontrolbutton',
btnClickCallback: this.toggleSound,
btnLabel: soundOn ? 'Mute' : 'UnMute',
})}
{VideoControlButton({
btnClassName:
'chat__videocallcontrolbutton chat__videocallcontrolbutton--videoonoff',
btnClickCallback: this.toggleVideo,
btnLabel: videoOn ? 'Turn Off Video' : 'Turn On Video',
})}
</div>
);
}
}

View file

@ -0,0 +1,25 @@
import { h, Component } from 'preact';
export default class VideoContent extends Component {
render() {
if (!this.props.videoPath) {
return ""
}
return (
<div
className="activechatchannel__activecontent activechatchannel__activecontent--video"
id="chat_activecontent_video"
>
<button
className="activechatchannel__activecontentexitbutton"
data-content="exit"
onClick={this.props.onExit}
>
×
</button>
<iframe src={this.props.videoPath} />
</div>
);
}
}

View file

@ -2,24 +2,27 @@ const { createLocalVideoTrack, connect } = require('twilio-video');
const root = document.getElementById('videochat');
const muteButton = document.getElementById('mute-toggle');
const videoToggleButton = document.getElementById('videohide-toggle');
let numConnected = 0;
connect(root.dataset.token, { name: 'room-name', audio: true, type: 'peer-to-peer', video: { width: 640 } }).then(room => {
const localMediaContainer = document.getElementById('local-media');
const roomType = document.getElementById('room-type').dataset.type//Group
connect(root.dataset.token, { name: 'room-name', audio: true, type: 'peer-to-peer', video: { width: 800 } }).then(room => {
room.participants.forEach(participantConnected);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
room.once('disconnected', error => room.participants.forEach(participantDisconnected));
console.log(room.participants.length)
muteButton.onclick = function(e) {
e.preventDefault();
room.localParticipant.audioTracks.forEach(function(trackPub) {
if (muteButton.dataset.muted === 'true') {
muteButton.dataset.muted = 'false'
muteButton.classList.remove('active');
muteButton.classList.remove('crayons-btn--danger');
muteButton.innerHTML = 'Mute'
trackPub.track.enable();
} else {
muteButton.dataset.muted = 'true'
muteButton.classList.add('active');
muteButton.classList.add('crayons-btn--danger');
muteButton.innerHTML = 'Unmute'
trackPub.track.disable();
}
});
@ -30,11 +33,15 @@ connect(root.dataset.token, { name: 'room-name', audio: true, type: 'peer-to-pee
room.localParticipant.videoTracks.forEach(function(trackPub) {
if (videoToggleButton.dataset.hidden === 'true') {
videoToggleButton.dataset.hidden = 'false'
videoToggleButton.classList.remove('active');
videoToggleButton.classList.remove('crayons-btn--danger');
videoToggleButton.innerHTML = 'Hide';
localMediaContainer.classList.remove('video-hidden');
trackPub.track.enable();
} else {
videoToggleButton.dataset.hidden = 'true'
videoToggleButton.classList.add('active');
videoToggleButton.classList.add('crayons-btn--danger');
videoToggleButton.innerHTML = 'Unhide';
localMediaContainer.classList.add('video-hidden');
trackPub.track.disable();
}
});
@ -42,9 +49,7 @@ connect(root.dataset.token, { name: 'room-name', audio: true, type: 'peer-to-pee
});
createLocalVideoTrack().then(track => {
const localMediaContainer = document.getElementById('local-media');
localMediaContainer.appendChild(track.attach());
});
@ -54,41 +59,61 @@ function participantConnected(participant) {
const div = document.createElement('div');
div.id = participant.sid;
div.className = "individual-video"
div.innerHTML = '<div class="participant-name">'+ participant.identity + '</div>'
div.innerHTML = '<div class="participant-info">\
<div class="participant-name">'+ participant.identity + '</div>\
<div class="disabled-audio-indicator">audio off</div>\
<div class="disabled-video-indicator">video off</div>\
</div>'
participant.on('trackSubscribed', track => trackSubscribed(div, track));
participant.on('trackUnsubscribed', trackUnsubscribed);
participant.on('trackDisabled', track => trackDisabled(div, track));
participant.on('trackEnabled', track => trackEnabled(div, track));
participant.tracks.forEach(publication => {
if (publication.isSubscribed) {
trackSubscribed(div, publication.track);
}
});
root.appendChild(div);
numConnected += 1;
let gridStyle = 'one-per';
if (numConnected > 2) {
gridStyle = 'two-per';
}
if (numConnected > 6) {
gridStyle = 'three-per';
}
if (numConnected > 9) {
gridStyle = 'four-per';
}
root.className = 'video-chat-wrapper video-chat-wrapper-num-' + gridStyle;
}
function participantDisconnected(participant) {
console.log('Participant "%s" disconnected', participant.identity);
document.getElementById(participant.sid).remove();
}
function trackSubscribed(div, track) {
div.appendChild(track.attach());
if (!track.isEnabled) {
if(track.kind === 'video') {
div.classList.add('disabled-video')
} else {
div.classList.add('disabled-audio')
}
}
}
function trackUnsubscribed(track) {
track.detach().forEach(element => element.remove());
}
function trackDisabled(div, track) {
if(track.kind === 'video') {
div.classList.add('disabled-video')
} else {
div.classList.add('disabled-audio')
}
}
function trackEnabled(div, track) {
if(track.kind === 'video') {
div.classList.remove('disabled-video')
} else {
div.classList.remove('disabled-audio')
}
}
if (navigator.userAgent === 'DEV-Native-ios') {
root.innerHTML = '<h2 class="platform-unavailable-message">This feature is not yet available on iPhone</h2>'
}

View file

@ -25,13 +25,7 @@ export default function setupPusher(key, callbackObjects) {
channel.bind('message-opened', callbackObjects.messageOpened);
channel.bind('channel-cleared', callbackObjects.channelCleared);
channel.bind('user-banned', callbackObjects.redactUserMessages);
channel.bind('client-livecode', callbackObjects.liveCoding);
channel.bind(
'client-initiatevideocall',
callbackObjects.videoCallInitiated,
);
channel.bind('client-endvideocall', callbackObjects.videoCallEnded);
channel.bind('pusher:subscription_error', callbackObjects.channelError);
return channel;

View file

@ -1,76 +1,109 @@
<style>
body {
height: 100vh;
overflow: hidden;
}
.video-chat-wrapper {
position: relative;
height: 100vh;
background: black;
background: #171717;
overflow: scroll;
}
video {
border-radius: 8px;
}
.individual-video video {
width: 96%;
max-width: 800px;
margin: 2%;
display: inline-block;
background: grey;
background: #232324;
width: 100%;
}
.video-chat-wrapper-num-two-per .individual-video {
width: 50%;
.individual-video {
overflow: hidden;
}
.video-chat-wrapper-num-three-per .individual-video {
width: 33%;
}
.video-chat-wrapper-num-four-per .individual-video {
width: 25%;
}
#local-media video {
width: 130px;
width: 150px;
position: absolute;
bottom: 10px;
right: 10px;
z-index: 5;
}
#local-media.video-hidden {
opacity: 0.1;
}
.individual-video {
position: relative;
display: inline-block;
margin-bottom: -5px;
}
.participant-name {
background: black;
.participant-info {
padding: 3px 8px;
border-radius: 3px;
position: absolute;
bottom: 4%;
left: 3%;
bottom: 0;
left: 0;
right: 0;
font-size: 13px;
text-align: center;
}
.participant-name {
display: inline-block;
color: white;
font-size: 16px;
background: #171717;
padding: 4px 12px;
border-radius: 100px;
margin: 8px;
}
.video-controls {
position: absolute;
bottom: 10px;
left: 10px;
bottom: 15px;
left: 5px;
width: calc(100% - 175px);
}
.video-controls button {
font-size: 16px;
color: white;
background: black;
border: 2px solid grey;
border-radius: 100px;
font-size: 15px;
width: 100%;
margin: 3px 0px;
padding: 3px 0px;
}
.video-controls button.active {
.disabled-video video {
opacity: 0.1;
}
.disabled-audio-indicator,.disabled-video-indicator {
background: red;
color: white;
display: none;
position: absolute;
border-radius: 100px;
padding: 4px 10px;
right: 5px;
bottom: 12px;
}
.disabled-audio-indicator {
right: auto;
left: 5px;
}
.disabled-video .disabled-video-indicator{
display: inline-block;
}
.disabled-audio .disabled-audio-indicator{
display: inline-block;
}
.platform-unavailable-message {
padding: 20px;
color: white;
margin-top: 30px;
}
</style>
<div class="video-chat-wrapper" id="videochat" data-token="<%= @token %>" data-channel="<%= params[:id] %>" data-user="<%= @username %>">
<div id="room-type" data-type="<%= @video_type %>"></div>
<div id="remote-media"></div>
<div id="local-media"></div>
<div class="video-controls">
<button id="mute-toggle" data-muted="false">Mute</button>
<button id="videohide-toggle" data-hidden="false">Hide Video</button>
<button id="mute-toggle" data-muted="false" class="crayons-btn crayons-btn--secondary">Mute</button>
<button id="videohide-toggle" data-hidden="false" class="crayons-btn crayons-btn--secondary">Hide</button>
</div>
<%# TODO Make paramsp[:id] map to a chat channel and ensure the users have permission %>
<%# That part is pretty straightforward %>