removed incomplete video conferences
This commit is contained in:
@@ -7,21 +7,12 @@ import { secondsToTime } from '../lib/strformat.js';
|
||||
|
||||
export default class CallMessage extends React.PureComponent {
|
||||
render() {
|
||||
const bigIcon = this.props.vc ?
|
||||
<div className="composed-material">
|
||||
<i className="material-icons gray second-small">group</i>
|
||||
<i className="material-icons gray big outline">call</i>
|
||||
</div>
|
||||
:
|
||||
<div><i className="material-icons big gray">call</i></div>;
|
||||
const isCallDropped = ['busy', 'declined', 'disconnected', 'missed'].includes(this.props.callState);
|
||||
const successClass = 'material-icons medium ' + (isCallDropped ? 'red' : 'green');
|
||||
const callIcon = this.props.incoming ?
|
||||
(isCallDropped ? 'call_missed' : 'call_received') :
|
||||
(isCallDropped ? 'call_missed_outgoing' : 'call_made');
|
||||
const title = this.props.vc ?
|
||||
<FormattedMessage id="calls_conference" defaultMessage="Conference call" description="Conference call label" /> :
|
||||
this.props.incoming ?
|
||||
const title = this.props.incoming ?
|
||||
<FormattedMessage id="calls_incoming" defaultMessage="Incoming call" description="Incoming call label" /> :
|
||||
<FormattedMessage id="calls_outgoing" defaultMessage="Outgoing call" description="Outgoing call label" />;
|
||||
let duration;
|
||||
@@ -45,17 +36,11 @@ export default class CallMessage extends React.PureComponent {
|
||||
} else {
|
||||
duration = <span>{secondsToTime(this.props.duration / 1000)}</span>;
|
||||
}
|
||||
const allowJoin = this.props.vc && this.props.callState == 'started';
|
||||
return <div className="call-message">
|
||||
<div><i className="material-icons big gray">{bigIcon}</i></div>
|
||||
<div><i className="material-icons big gray">call</i></div>
|
||||
<div className="flex-column narrow">
|
||||
<div>{title}</div>
|
||||
<div className="duration"><i className={successClass}>{callIcon}</i> {duration}</div>
|
||||
{allowJoin ?
|
||||
<a onClick={this.props.onCallJoin}>
|
||||
<FormattedMessage id="join_conference" defaultMessage="+ Join" description="Call to action to join a conference call" />
|
||||
</a>
|
||||
: null}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ class BaseChatMessage extends React.PureComponent {
|
||||
this.handleContextClick = this.handleContextClick.bind(this);
|
||||
this.handleCancelUpload = this.handleCancelUpload.bind(this);
|
||||
this.handleQuoteClick = this.handleQuoteClick.bind(this);
|
||||
this.handleCallJoin = this.handleCallJoin.bind(this);
|
||||
|
||||
this.formatterContext = {
|
||||
formatMessage: props.intl.formatMessage.bind(props.intl),
|
||||
@@ -39,8 +38,7 @@ class BaseChatMessage extends React.PureComponent {
|
||||
onImagePreview: this.handleExpandImage,
|
||||
onVideoPreview: this.handlePlayVideo,
|
||||
onFormButtonClick: this.handleFormButtonClick,
|
||||
onQuoteClick: this.handleQuoteClick,
|
||||
onCallJoin: this.handleCallJoin
|
||||
onQuoteClick: this.handleQuoteClick
|
||||
};
|
||||
}
|
||||
|
||||
@@ -151,10 +149,6 @@ class BaseChatMessage extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleCallJoin() {
|
||||
this.props.onAcceptCall(this.props.topic, true, this.props.seq);
|
||||
}
|
||||
|
||||
render() {
|
||||
const sideClass = this.props.sequence + ' ' + (this.props.response ? 'left' : 'right');
|
||||
const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Video conference carousel item.
|
||||
import React from 'react';
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
|
||||
/**
|
||||
* Props:
|
||||
* - cameraPub
|
||||
* - micPub
|
||||
* - isSpeaking
|
||||
* - tinode
|
||||
* - photo
|
||||
* - userId
|
||||
* - name
|
||||
*/
|
||||
export default class VCCarouselItem extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleVideoRefChange = this.handleVideoRefChange.bind(this);
|
||||
this.handleAudioRefChange = this.handleAudioRefChange.bind(this);
|
||||
|
||||
this.videoRef = null;
|
||||
this.audioRef = null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.videoRef && !this.videoRef.srcObject && this.props.cameraPub && this.props.cameraPub.videoTrack) {
|
||||
this.props.cameraPub.videoTrack.attach(this.videoRef);
|
||||
}
|
||||
if (this.audioRef && !this.audioRef.srcObject && this.props.micPub && this.props.micPub.audioTrack) {
|
||||
this.props.micPub.audioTrack.attach(this.audioRef);
|
||||
}
|
||||
}
|
||||
|
||||
handleVideoRefChange(node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.videoRef = node;
|
||||
|
||||
// Attach video track to video element.
|
||||
const cameraPub = this.props.cameraPub;
|
||||
const cameraEnabled = cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
|
||||
if (cameraEnabled && cameraPub.videoTrack) {
|
||||
cameraPub.videoTrack.attach(node);
|
||||
}
|
||||
}
|
||||
|
||||
handleAudioRefChange(node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.audioRef = node;
|
||||
|
||||
// Attach audio track to audio element.
|
||||
const micPub = this.props.micPub;
|
||||
const micEnabled = micPub && micPub.isSubscribed && !micPub.isMuted;
|
||||
if (micEnabled && micPub.audioTrack) {
|
||||
micPub.audioTrack.attach(node);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = 'call-party carousel-item';
|
||||
if (this.props.isSpeaking) {
|
||||
classes += ' call-party-speaking';
|
||||
}
|
||||
|
||||
const micPub = this.props.micPub;
|
||||
const cameraPub = this.props.cameraPub;
|
||||
return (
|
||||
<div className={classes}>
|
||||
<video ref={this.handleVideoRefChange}></video>
|
||||
<audio ref={this.handleAudioRefChange}></audio>
|
||||
{micPub && micPub.isMuted ?
|
||||
<i className="material-icons muted">mic_off</i>
|
||||
: null
|
||||
}
|
||||
{cameraPub && cameraPub.isMuted ?
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.props.photo || true}
|
||||
topic={this.props.userId}
|
||||
title={this.props.name} />
|
||||
</div>
|
||||
: null}
|
||||
<div className="caller-name inactive">
|
||||
{this.props.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Track } from 'livekit-client';
|
||||
|
||||
import VCCarouselItem from './vc-carousel-item.jsx';
|
||||
|
||||
import { VC_CAROUSEL_ITEM_GAP } from '../constants.js';
|
||||
|
||||
export default class VCCarousel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
prevVisible: false,
|
||||
nextVisible: false
|
||||
};
|
||||
|
||||
this.handleScrollReference = this.handleScrollReference.bind(this);
|
||||
this.handleScrollEvent = this.handleScrollEvent.bind(this);
|
||||
this.handleScrollClick = this.handleScrollClick.bind(this);
|
||||
|
||||
this.carouselRef = null;
|
||||
this.contentRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.carouselRef && this.props.viewportWidth != prevProps.viewportWidth) {
|
||||
this.handleScrollEvent({target: this.carouselRef});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.carouselRef) {
|
||||
this.carouselRef.removeEventListener('scroll', this.handleScrollEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't use React.createRef as the ref.current is not available in componentDidMount in this component.
|
||||
handleScrollReference(node) {
|
||||
if (node) {
|
||||
this.carouselRef = node;
|
||||
this.carouselRef.addEventListener('scroll', this.handleScrollEvent);
|
||||
this.handleScrollEvent({target: this.carouselRef});
|
||||
}
|
||||
}
|
||||
|
||||
// Get older messages and show/hide [go to latest message] button.
|
||||
handleScrollEvent(event) {
|
||||
this.setState({
|
||||
// Show [prev] / [next] as appropriate.
|
||||
prevVisible: event.target.scrollLeft > VC_CAROUSEL_ITEM_GAP,
|
||||
nextVisible: event.target.scrollWidth - event.target.scrollLeft - event.target.offsetWidth > VC_CAROUSEL_ITEM_GAP
|
||||
});
|
||||
}
|
||||
|
||||
handleScrollClick(next) {
|
||||
// this.carouselRef.offsetWidth - is the width of the visible window.
|
||||
const val = this.carouselRef.offsetWidth + VC_CAROUSEL_ITEM_GAP;
|
||||
this.carouselRef.scrollBy(next ? val : -val, 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
let peers = [];
|
||||
if (this.props.participants) {
|
||||
Object.entries(this.props.participants).forEach(([identity, party], i) => {
|
||||
const participant = party.participant;
|
||||
const cameraPub = participant.getTrack(Track.Source.Camera);
|
||||
const micPub = participant.getTrack(Track.Source.Microphone);
|
||||
|
||||
peers.push(<VCCarouselItem
|
||||
tinode={this.props.tinode}
|
||||
isSpeaking={participant.isSpeaking}
|
||||
cameraPub={cameraPub ? cameraPub : null}
|
||||
micPub={micPub ? micPub : null}
|
||||
name={party.name}
|
||||
userId={identity}
|
||||
photo={party.photo}
|
||||
key={i}
|
||||
/>);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div id="carousel-wrapper">
|
||||
<div id="carousel" ref={this.handleScrollReference}>
|
||||
<div id="carousel-content" ref={this.contentRef}>
|
||||
{peers}
|
||||
</div>
|
||||
</div>
|
||||
{this.state.prevVisible ?
|
||||
<button id="carousel-prev" className="action-button" onClick={_ => this.handleScrollClick(false)}>
|
||||
<i className="material-icons">navigate_before</i>
|
||||
</button>
|
||||
:
|
||||
null}
|
||||
{this.state.nextVisible ?
|
||||
<button id="carousel-next" className="action-button" onClick={_ => this.handleScrollClick(true)}>
|
||||
<i className="material-icons">navigate_next</i>
|
||||
</button>
|
||||
:
|
||||
null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
// Video conference or broadcast: local and/or peer viewports and controls.
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
LocalVideoTrack,
|
||||
MediaDeviceFailure,
|
||||
ParticipantEvent,
|
||||
RemoteParticipant,
|
||||
Room,
|
||||
RoomEvent,
|
||||
Track,
|
||||
VideoPresets
|
||||
} from 'livekit-client';
|
||||
|
||||
import LetterTile from './letter-tile.jsx';
|
||||
import VCCarousel from './vc-carousel.jsx';
|
||||
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||
|
||||
import { MAX_PEER_TITLE_LENGTH } from '../config.js';
|
||||
import { CALL_STATE_OUTGOING_INITATED } from '../constants.js';
|
||||
|
||||
import { clipStr } from '../lib/utils.js'
|
||||
|
||||
class VCPanel extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
room: undefined,
|
||||
|
||||
participants: {},
|
||||
activeSpeaker: undefined,
|
||||
|
||||
previousOnInfo: undefined,
|
||||
};
|
||||
|
||||
this.isOutgoingCall = props.callState == CALL_STATE_OUTGOING_INITATED;
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
this.isBroadcast = topic && topic.chan;
|
||||
|
||||
this.localRef = React.createRef();
|
||||
this.remoteRef = React.createRef();
|
||||
|
||||
this.onInfo = this.onInfo.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
this.stop = this.stop.bind(this);
|
||||
|
||||
this.reportError = this.reportError.bind(this);
|
||||
|
||||
this.handleCloseClick = this.handleCloseClick.bind(this);
|
||||
this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this);
|
||||
this.handleToggleMicClick = this.handleToggleMicClick.bind(this);
|
||||
|
||||
this.handleRemoteHangup = this.handleRemoteHangup.bind(this);
|
||||
|
||||
this.handleTrackSubscribed = this.handleTrackSubscribed.bind(this);
|
||||
this.handleTrackUnsubscribed = this.handleTrackUnsubscribed.bind(this);
|
||||
this.handleActiveSpeakerChange = this.handleActiveSpeakerChange.bind(this);
|
||||
this.handleDisconnect = this.handleDisconnect.bind(this);
|
||||
this.handleLocalTrackUnpublished = this.handleLocalTrackUnpublished.bind(this);
|
||||
this.handleLocalTrackPublished = this.handleLocalTrackPublished.bind(this);
|
||||
this.handleParticipantConnected = this.handleParticipantConnected.bind(this);
|
||||
this.handleParticipantDisconnected = this.handleParticipantDisconnected.bind(this);
|
||||
this.handleData = this.handleData.bind(this);
|
||||
this.handleDevicesChanged = this.handleDevicesChanged.bind(this);
|
||||
this.startRoom = this.startRoom.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
this.previousOnInfo = topic.onInfo;
|
||||
topic.onInfo = this.onInfo;
|
||||
this.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
if (topic) {
|
||||
topic.onInfo = this.previousOnInfo;
|
||||
}
|
||||
this.stop();
|
||||
}
|
||||
|
||||
onInfo(info) {
|
||||
if (info.what != 'call') {
|
||||
return;
|
||||
}
|
||||
switch (info.event) {
|
||||
case 'vc-token':
|
||||
const ts = info.payload;
|
||||
this.setState({token: ts.token});
|
||||
this.startRoom(ts.token);
|
||||
break;
|
||||
case 'hang-up':
|
||||
this.handleRemoteHangup(info);
|
||||
break;
|
||||
default:
|
||||
console.warn("Unknown call event", info.event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handleParticipantConnected(participant) {
|
||||
if (!participant) return;
|
||||
if (participant instanceof RemoteParticipant) {
|
||||
participant
|
||||
.on(ParticipantEvent.TrackMuted, pub => {
|
||||
console.log('track was muted', pub.trackSid, participant.identity);
|
||||
this.forceUpdate(); // Redraw carousel.
|
||||
})
|
||||
.on(ParticipantEvent.TrackUnmuted, pub => {
|
||||
console.log('track was unmuted', pub.trackSid, participant.identity);
|
||||
this.forceUpdate(); // Redraw carousel.
|
||||
})
|
||||
.on(ParticipantEvent.IsSpeakingChanged, _ => {
|
||||
this.forceUpdate(); // Redraw carousel.
|
||||
})
|
||||
.on(ParticipantEvent.ConnectionQualityChanged, _ => {
|
||||
this.forceUpdate(); // Redraw carousel.
|
||||
});
|
||||
|
||||
const me = this.props.tinode.getMeTopic();
|
||||
const c = me.getContact(participant.identity);
|
||||
|
||||
const participants = Object.assign({}, this.state.participants);
|
||||
participants[participant.identity] = {
|
||||
name: c && c.public && c.public.fn ? c.public.fn : participant.identity,
|
||||
photo: makeImageUrl(c && c.public ? c.public.photo : null),
|
||||
participant: participant
|
||||
};
|
||||
this.setState({participants: participants});
|
||||
}
|
||||
}
|
||||
|
||||
handleParticipantDisconnected(participant) {
|
||||
const participants = Object.assign({}, this.state.participants);
|
||||
delete participants[participant.identity];
|
||||
this.setState({participants: participants});
|
||||
}
|
||||
|
||||
handleData(msg, participant) {
|
||||
console.log('Data message ', msg, participant);
|
||||
}
|
||||
|
||||
handleTrackSubscribed(track, publication, participant) {
|
||||
if (participant instanceof RemoteParticipant) {
|
||||
// Check if we're presenting a remote video stream.
|
||||
if (this.remoteRef.current && !(this.remoteRef.current.src != '' || this.remoteRef.srcObject)) {
|
||||
track.attach(this.remoteRef.current);
|
||||
}
|
||||
}
|
||||
// Redraw the carousel and remote view.
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
handleTrackUnsubscribed(track, publication, participant) {
|
||||
console.log('handleTrackUnsubscribed', track);
|
||||
// remove tracks from all attached elements
|
||||
track.detach();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
handleLocalTrackPublished(pub) {
|
||||
console.log('Local track published', pub);
|
||||
const track = pub.track;
|
||||
if ((track instanceof LocalVideoTrack) && this.localRef.current) {
|
||||
const elem = this.localRef.current;
|
||||
track.attach(elem);
|
||||
// flip.
|
||||
elem.style.transform = 'scale(-1, 1)';
|
||||
}
|
||||
}
|
||||
|
||||
handleLocalTrackUnpublished(pub) {
|
||||
const track = pub.track;
|
||||
console.log('handleLocalTrackUnpublished', track);
|
||||
track.detach();
|
||||
// Redraw carousel.
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
handleActiveSpeakerChange(speakers) {
|
||||
console.log('handleActiveSpeakerChange', speakers);
|
||||
speakers.forEach(s => {
|
||||
if (s.isSpeaking) {
|
||||
console.log('Speaking participant ', s);
|
||||
|
||||
// Display speaker in remote peer view.
|
||||
const elem = this.remoteRef.current;
|
||||
const me = this.props.tinode.getMeTopic();
|
||||
const c = me.getContact(s.identity);
|
||||
const name = c && c.public && c.public.fn ? c.public.fn : undefined;
|
||||
this.setState({activeSpeaker: name});
|
||||
|
||||
const cameraPub = s.getTrack(Track.Source.Camera);
|
||||
const cameraEnabled = cameraPub && cameraPub.isSubscribed && !cameraPub.isMuted;
|
||||
if (cameraEnabled && cameraPub.videoTrack) {
|
||||
cameraPub.videoTrack.attach(elem);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleDevicesChanged() {
|
||||
console.log('devices changed');
|
||||
// TODO: implement.
|
||||
}
|
||||
|
||||
handleDisconnect() {
|
||||
console.log('disconnected from room');
|
||||
this.handleCloseClick();
|
||||
}
|
||||
|
||||
startRoom(token) {
|
||||
const roomOpts = {
|
||||
adaptiveStream: true,
|
||||
dynacast: true,
|
||||
publishDefaults: {
|
||||
simulcast: true,
|
||||
videoSimulcastLayers: [VideoPresets.h90, VideoPresets.h216],
|
||||
videoCodec: 'vp8'
|
||||
},
|
||||
videoCaptureDefaults: {
|
||||
resolution: VideoPresets.h720.resolution
|
||||
}
|
||||
};
|
||||
|
||||
const connectOpts = {
|
||||
autoSubscribe: true
|
||||
};
|
||||
|
||||
// creates a new room with options
|
||||
const room = new Room(roomOpts);
|
||||
|
||||
this.setState({room: room});
|
||||
|
||||
const startTime = Date.now();
|
||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||
const shouldPublish = this.isOutgoingCall || !this.isBroadcast;
|
||||
|
||||
room
|
||||
.on(RoomEvent.ParticipantConnected, this.handleParticipantConnected)
|
||||
.on(RoomEvent.ParticipantDisconnected, this.handleParticipantDisconnected)
|
||||
.on(RoomEvent.DataReceived, this.handleData)
|
||||
.on(RoomEvent.TrackSubscribed, this.handleTrackSubscribed)
|
||||
.on(RoomEvent.TrackUnsubscribed, this.handleTrackUnsubscribed)
|
||||
.on(RoomEvent.ActiveSpeakersChanged, this.handleActiveSpeakerChange)
|
||||
.on(RoomEvent.Disconnected, this.handleDisconnect)
|
||||
.on(RoomEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
|
||||
.on(RoomEvent.LocalTrackPublished, this.handleLocalTrackPublished)
|
||||
|
||||
.on(RoomEvent.RoomMetadataChanged, metadata => {
|
||||
console.log('VC room new metadata ', metadata);
|
||||
})
|
||||
.on(RoomEvent.MediaDevicesChanged, this.handleDevicesChanged)
|
||||
.on(RoomEvent.AudioPlaybackStatusChanged, e => {
|
||||
// TODO: handle it.
|
||||
console.log('Audio playback status change: ', e);
|
||||
})
|
||||
.on(RoomEvent.MediaDevicesError, e => {
|
||||
const failure = MediaDeviceFailure.getFailure(e);
|
||||
this.reportError(failure);
|
||||
})
|
||||
.on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
||||
const id = participant ? participant.identity : 'nil';
|
||||
console.log('Connection quality changed', id, ' -> ', quality);
|
||||
})
|
||||
.on(RoomEvent.SignalConnected, _ => {
|
||||
const signalConnectionTime = Date.now() - startTime;
|
||||
console.log(`signal connection established in ${signalConnectionTime}ms`);
|
||||
// speed up publishing by starting to publish before it's fully connected
|
||||
// publishing is accepted as soon as signal connection has established
|
||||
if (shouldPublish) {
|
||||
room.localParticipant.enableCameraAndMicrophone();
|
||||
console.log(`tracks published in ${Date.now() - startTime}ms`);
|
||||
}
|
||||
});
|
||||
|
||||
const vcEndpoint = this.props.tinode.getServerParam('vcEndpoint', null);
|
||||
room.connect(vcEndpoint, token, connectOpts).then(_ => {
|
||||
console.log('Connected to VC room', room.name, '. Access token', token);
|
||||
|
||||
room.participants.forEach((participant) => {
|
||||
this.handleParticipantConnected(participant);
|
||||
});
|
||||
this.handleParticipantConnected(room.localParticipant);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.state.localStream) {
|
||||
this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info');
|
||||
return;
|
||||
}
|
||||
if (this.isOutgoingCall) {
|
||||
this.props.onCreateCall(this.props.topic, this.props.seq, this.props.callState, this.props.callAudioOnly)
|
||||
.then(ctrl => {
|
||||
const token = ctrl.params.token;
|
||||
const seq = ctrl.params.seq;
|
||||
this.setState({callSeq: seq, token: token});
|
||||
this.startRoom(token);
|
||||
});
|
||||
} else {
|
||||
this.props.onJoin(this.props.topic, this.props.seq);
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.state.room) {
|
||||
this.state.room.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
reportError(err) {
|
||||
this.props.onError(err.message, 'err');
|
||||
}
|
||||
|
||||
// Call disconnected by remote.
|
||||
handleRemoteHangup() {
|
||||
this.handleCloseClick();
|
||||
}
|
||||
|
||||
handleCloseClick() {
|
||||
this.stop();
|
||||
this.props.onHangup(this.props.topic, this.props.seq);
|
||||
}
|
||||
|
||||
handleToggleCameraClick() {
|
||||
if (!this.state.room) return;
|
||||
const enabled = this.state.room.localParticipant.isCameraEnabled;
|
||||
this.state.room.localParticipant.setCameraEnabled(!enabled)
|
||||
.then(_ => this.forceUpdate());
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
handleToggleMicClick() {
|
||||
if (!this.state.room) return;
|
||||
const enabled = this.state.room.localParticipant.isMicrophoneEnabled;
|
||||
this.state.room.localParticipant.setMicrophoneEnabled(!enabled)
|
||||
.then(_ => this.forceUpdate());
|
||||
|
||||
// Make sure we redraw the mute/unmute icons (e.g. mic -> mic_off).
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
const isPublishing = this.isOutgoingCall || !this.isBroadcast;
|
||||
const disabled = !isPublishing || !this.state.room || !this.state.room.localParticipant;
|
||||
const audioIcon = !disabled && this.state.room.localParticipant.isMicrophoneEnabled ? 'mic' : 'mic_off';
|
||||
const videoIcon = !disabled && this.state.room.localParticipant.isCameraEnabled ? 'videocam' : 'videocam_off';
|
||||
|
||||
const peerTitle = this.state.activeSpeaker ? this.state.activeSpeaker : clipStr(this.props.title, MAX_PEER_TITLE_LENGTH);
|
||||
|
||||
const remoteActive = this.remoteRef.current && (this.remoteRef.current.src || this.remoteRef.current.srcObject)
|
||||
|
||||
return (
|
||||
<div id="video-container">
|
||||
{!this.isBroadcast && Object.keys(this.state.participants).length > 0 ?
|
||||
<VCCarousel
|
||||
viewportWidth={this.props.viewportWidth}
|
||||
tinode={this.props.tinode}
|
||||
participants={this.state.participants} /> :
|
||||
null
|
||||
}
|
||||
<div id="video-container-panel">
|
||||
{isPublishing ?
|
||||
<div className="call-party self" disabled={this.state.audioOnly}>
|
||||
<video ref={this.localRef} autoPlay muted playsInline />
|
||||
<div className="caller-name inactive">
|
||||
<FormattedMessage id="calls_you_label"
|
||||
defaultMessage="You" description="Shown over the local video screen" />
|
||||
</div>
|
||||
</div> :
|
||||
null }
|
||||
<div className="call-party peer" disabled={!remoteActive}>
|
||||
<video ref={this.remoteRef} autoPlay playsInline />
|
||||
{remoteActive ?
|
||||
<div className="caller-name inactive">{peerTitle}</div>
|
||||
:
|
||||
<div className="caller-card">
|
||||
<div className="avatar-box">
|
||||
<LetterTile
|
||||
tinode={this.props.tinode}
|
||||
avatar={this.props.avatar}
|
||||
topic={this.props.topic}
|
||||
title={this.props.title} />
|
||||
</div>
|
||||
<div className="caller-name">{peerTitle}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="controls">
|
||||
<button className="danger" onClick={this.handleCloseClick}>
|
||||
<i className="material-icons">call_end</i>
|
||||
</button>
|
||||
<button className="secondary" onClick={this.handleToggleCameraClick} disabled={disabled}>
|
||||
<i className="material-icons">{videoIcon}</i>
|
||||
</button>
|
||||
<button className="secondary" onClick={this.handleToggleMicClick} disabled={disabled}>
|
||||
<i className="material-icons">{audioIcon}</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default injectIntl(VCPanel);
|
||||
Reference in New Issue
Block a user