Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Project/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,13 @@ div.volumeVisualizer::before {
transform: translateZ(0);
}

.scrollable-rtt-container {
overflow: auto;
max-height: 300px;
display: flex;
flex-direction: column-reverse;
}

.custom-video-effects-buttons:not(.outgoing) {
display: flex;
position: absolute;
Expand Down
121 changes: 121 additions & 0 deletions Project/src/MakeCall/CallCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import CallCaption from "./CallCaption";
import Lobby from "./Lobby";
import { ParticipantMenuOptions } from './ParticipantMenuOptions';
import MediaConstraint from './MediaConstraint';
import RealTimeTextCard from "./RealTimeTextCard";

export default class CallCard extends React.Component {
constructor(props) {
Expand All @@ -40,6 +41,9 @@ export default class CallCard extends React.Component {
this.raiseHandFeature = this.call.feature(Features.RaiseHand);
this.capabilitiesFeature = this.call.feature(Features.Capabilities);
this.capabilities = this.capabilitiesFeature.capabilities;
if (Features.RealTimeText) {
this.realTimeTextFeature = this.call.feature(Features.RealTimeText);
}
this.dominantSpeakersFeature = this.call.feature(Features.DominantSpeakers);
this.recordingFeature = this.call.feature(Features.Recording);
this.transcriptionFeature = this.call.feature(Features.Transcription);
Expand Down Expand Up @@ -86,6 +90,9 @@ export default class CallCard extends React.Component {
callMessage: undefined,
dominantSpeakerMode: false,
captionOn: false,
realTimeTextOn: false,
firstRealTimeTextReceivedorSent: false,
showCanNotHideorCloseRealTimeTextBanner: false,
dominantRemoteParticipant: undefined,
logMediaStats: false,
sentResolution: '',
Expand All @@ -111,6 +118,10 @@ export default class CallCard extends React.Component {
this.isSetCallConstraints = this.call.setConstraints !== undefined;
}

setFirstRealTimeTextReceivedorSent = (state) => {
this.setState({ firstRealTimeTextReceivedorSent: state });
}

componentWillUnmount() {
this.call.off('stateChanged', () => { });
this.deviceManager.off('videoDevicesUpdated', () => { });
Expand Down Expand Up @@ -468,6 +479,7 @@ export default class CallCard extends React.Component {
this.recordingFeature.on('isRecordingActiveChanged', this.isRecordingActiveChangedHandler);
this.transcriptionFeature.on('isTranscriptionActiveChanged', this.isTranscriptionActiveChangedHandler);
this.lobby?.on('lobbyParticipantsUpdated', this.lobbyParticipantsUpdatedHandler);
this.realTimeTextFeature?.on('realTimeTextReceived', this.realTimeTextReceivedHandler);
}
}

Expand Down Expand Up @@ -649,6 +661,62 @@ export default class CallCard extends React.Component {
this.capabilities = this.capabilitiesFeature.capabilities;
}

realTimeTextReceivedHandler = (rttData) => {
this.setState({ realTimeTextOn: true });
if (!this.state.firstRealTimeTextReceivedorSent) {
this.setState({ firstRealTimeTextReceivedorSent: true });
}
if (rttData) {

let mri = '';
let displayName = '';
switch (rttData.sender.identifier.kind) {
case 'communicationUser': { mri = rttData.sender.identifier.communicationUserId; displayName = rttData.sender.displayName; break; }
case 'microsoftTeamsUser': { mri = rttData.sender.identifier.microsoftTeamsUserId; displayName = rttData.sender.displayName; break; }
case 'phoneNumber': { mri = rttData.sender.identifier.phoneNumber; displayName = rttData.sender.displayName; break; }
}

let rttAreaContainer = document.getElementById('rttArea');

const newClassName = `prefix${mri.replace(/:/g, '').replace(/-/g, '').replace(/\+/g, '')}`;
const rttText = `${(rttData.receivedTimestamp).toUTCString()} ${displayName ?? mri} isTyping: `;

let foundRTTContainer = rttAreaContainer.querySelector(`.${newClassName}[isNotFinal='true']`);

if (!foundRTTContainer) {
if (rttData.text.trim() === '') {
return
}
let rttContainer = document.createElement('div');
rttContainer.setAttribute('isNotFinal', 'true');
rttContainer.style['borderBottom'] = '1px solid';
rttContainer.style['whiteSpace'] = 'pre-line';
rttContainer.textContent = rttText + rttData.text;
rttContainer.classList.add(newClassName);

rttAreaContainer.appendChild(rttContainer);

setTimeout(() => {
rttAreaContainer.removeChild(rttContainer);
}, 40000);
} else {
if (rttData.text.trim() === '') {
rttAreaContainer.removeChild(foundRTTContainer);
}
if (rttData.resultType === 'Final') {
foundRTTContainer.setAttribute('isNotFinal', 'false');
foundRTTContainer.textContent = foundRTTContainer.textContent.replace(' isTyping', '');
if (rttData.isLocal) {
let rttTextField = document.getElementById('rttTextField');
rttTextField.value = null;
}
} else {
foundRTTContainer.textContent = rttText + rttData.text;
}
}
}
}

dominantSpeakersChanged = () => {
const dominantSpeakersMris = this.dominantSpeakersFeature.dominantSpeakers.speakersList;
const remoteParticipants = dominantSpeakersMris.map(dominantSpeakerMri => {
Expand Down Expand Up @@ -1455,6 +1523,27 @@ export default class CallCard extends React.Component {
<Icon iconName="TextBox" />
}
</span>
{ Features.RealTimeText &&
<span className="in-call-button"
title={`${this.state.realTimeTextOn ? 'Hide RealTimeText Card' : 'Show RealTimeText Card'}`}
variant="secondary"
hidden={this.state.callState !== 'Connected'}
onClick={() => {
if (!this.state.firstRealTimeTextReceivedorSent) {
this.setState((prevState) => ({ realTimeTextOn: !prevState.realTimeTextOn }))
} else {
this.setState((prevState) => ({ showCanNotHideorCloseRealTimeTextBanner: true}))
}}}>
{
this.state.realTimeTextOn &&
<Icon iconName="Comment" />
}
{
!this.state.realTimeTextOn &&
<Icon iconName="Comment" />
}
</span>
}
<span className="in-call-button"
title={`${this.state.showDataChannel ? 'Turn data channel off' : 'Turn data channel on'}`}
variant="secondary"
Expand Down Expand Up @@ -1741,6 +1830,38 @@ export default class CallCard extends React.Component {
</div>
</div>
}
{
Features.RealTimeText && this.state.realTimeTextOn &&
<div className="mt-5">
<div className="ms-Grid-row">
<h3>RealTimeText</h3>
</div>
<div className="md-grid-row">
{
this.state.realTimeTextOn &&
this.state.firstRealTimeTextReceivedorSent &&
this.state.showCanNotHideorCloseRealTimeTextBanner &&
<MessageBar
messageBarType={MessageBarType.warn}
isMultiline={true}
onDismiss={() => { this.setState({ showCanNotHideorCloseRealTimeTextBanner: undefined }) }}
dismissButtonAriaLabel="Close">
<b>Note: RealTimeText can not be closed or hidden after you have sent or received a message.</b>
</MessageBar>
}
{
this.state.realTimeTextOn &&
<RealTimeTextCard
call={this.call}
state={{
firstRealTimeTextReceivedorSent: this.state.firstRealTimeTextReceivedorSent,
setFirstRealTimeTextReceivedorSent: this.setFirstRealTimeTextReceivedorSent
}}
/>
}
</div>
</div>
}
{
this.state.showDataChannel &&
<div className="mt-5">
Expand Down
97 changes: 97 additions & 0 deletions Project/src/MakeCall/RealTimeTextCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useEffect, useState } from "react";
import { Features } from '@azure/communication-calling';
import { PrimaryButton } from 'office-ui-fabric-react/lib/components/Button';

// RealTimeText react function component
const RealTimeTextCard = ({ call, state }) => {
const [realTimeTextFeature, setRealTimeTextFeature] = useState(call.feature(Features.RealTimeText));
const [rttInputLiveHandler, setRttInputLiveHandler] = useState(false);

useEffect(() => {
try {
subscribeToSendRealTimeTextLive();
}
catch(error) {
console.log("RealTimeText not configured for this release version")
}

return () => {
// cleanup
let rttTextField = document.getElementById('rttTextField');
rttTextField.removeEventListener('input', subscribeToSendRealTimeTextHelper);
};
}, []);

const sendRTT = async () => {
try {
let rttTextField = document.getElementById('rttTextField');
if (!state.firstRealTimeTextReceivedorSent) {
state.setFirstRealTimeTextReceivedorSent(true);
}
realTimeTextFeature.sendRealTimeText(rttTextField.value, true);
rttTextField.value = null;
} catch (error) {
console.log('ERROR Send RTT failed', error);
}
}

const sendRealTimeTextLiveHandler = () => {
if (!rttInputLiveHandler) {
try {
let rttTextField = document.getElementById('rttTextField');
rttTextField.removeEventListener('input', subscribeToSendRealTimeTextHelper);
rttTextField.addEventListener('input', (event) => {
if (!state.firstRealTimeTextReceivedorSent) {
state.setFirstRealTimeTextReceivedorSent(true);
}
realTimeTextFeature.sendRealTimeText(rttTextField.value);
})
setRttInputLiveHandler(true);
} catch (error) {
console.log('ERROR Send live rtt handler subscription failed', error);
}
}
}

const subscribeToSendRealTimeTextHelper = () => {
let rttTextField = document.getElementById('rttTextField');
if (rttTextField.value !== '') {
sendRealTimeTextLiveHandler();
}
setRttInputLiveHandler(true);
}

const subscribeToSendRealTimeTextLive = () => {
if (!rttInputLiveHandler) {
try {
let rttTextField = document.getElementById('rttTextField');
rttTextField.removeEventListener('input', subscribeToSendRealTimeTextHelper);
rttTextField.addEventListener('input', subscribeToSendRealTimeTextHelper);
} catch (error) {
console.log('ERROR setting live rtt handler', error);
}

}
}

return (
<>
<div>
<form style={{padding: '1rem 0'}}>
<label style={{display:'block'}}>RealTimeText Message</label>
<input
id='rttTextField'
style={{padding: '0.4rem', width: '15rem'}}
/>
<PrimaryButton text="Send" onClick={sendRTT}/>
</form>
</div>
<div className="scrollable-rtt-container">
<div id="rttArea" className="rtt-area">
</div>
</div>
</>
);
};

export default RealTimeTextCard;
Loading