Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"bandwidth-rtc": "^0.5.0",
"bandwidth-rtc": "^0.6.0",
"bandwidth-sdk": "^7.3.0",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
Expand Down
47 changes: 39 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import './css/App.scss';
import Navbar from "./components/Navbar";
import EndpointHandler from "./components/EndpointHandler";
import MediaCapture from "./components/MediaCapture";
import BandwidthRtc, {ReadyMetadata} from "bandwidth-rtc";
import BandwidthRtc, {ReadyMetadata, RtcStream} from "bandwidth-rtc";
import MediaPlayer from "./components/MediaPlayer";
import CallController from "./components/CallController";
import IncomingCall from "./components/IncomingCall";

function App() {

Expand All @@ -14,6 +15,12 @@ function App() {
const [readyMetadata, setReadyMetadata] = useState<ReadyMetadata | null>(null);
const [inCall, setInCall] = useState(false);
const [inboundStream, setInboundStream] = useState<MediaStream | null>(null);
// When autoAccept is off, an incoming stream that the gateway did not
// auto-accept lands here so we can prompt the user with Accept/Decline.
const [incomingCall, setIncomingCall] = useState<RtcStream | null>(null);
// Sent to the gateway at connect time via setMediaPreferences. The gateway
// echoes its decision back on each stream (RtcStream.autoAccepted).
const [autoAccept, setAutoAccept] = useState(false);

const prepBrtcClient= async (reset: boolean) => {
console.log("Prepping Bandwidth RTC Client")
Expand All @@ -24,6 +31,7 @@ function App() {
setBrtcClient(null)
setInCall(false)
setInboundStream(null)
setIncomingCall(null)
}
if (!brtcClient || reset) {
console.log("Creating Bandwidth RTC Client")
Expand All @@ -33,17 +41,23 @@ function App() {
// readyMetadata (and therefore MediaPlayer) exists.
brtcClient.onStreamAvailable((s) => {
console.log("Stream available:", s);
setInboundStream(s.mediaStream);
// The stream arriving means the call actually connected (answered),
// so we're truly in-call now — not just ringing.
setInCall(true);
// The gateway rides its accept decision on the stream itself. When it
// auto-accepted, connect straight through; otherwise prompt the user
// and wait for acceptStream/declineStream before playing any audio.
if (s.autoAccepted) {
setInboundStream(s.mediaStream);
setInCall(true);
} else {
setIncomingCall(s);
}
})
brtcClient.onStreamUnavailable((s) => {
console.log("Stream unavailable:", s);
// The far side (or gateway) ended/declined the call; reset the UI out
// of the in-call/ringing state.
setInboundStream(null);
// The far side (or gateway) ended the call; reset the UI out of
// the in-call/ringing state.
setInCall(false);
setIncomingCall(null);
})
brtcClient.onReady((readyMetadata: ReadyMetadata) => {
console.log("Ready Metadata:", readyMetadata);
Expand All @@ -62,6 +76,20 @@ function App() {
await prepBrtcClient(true)
}

const handleAccept = async () => {
if (!brtcClient || !incomingCall) return;
await brtcClient.acceptStream();
setInboundStream(incomingCall.mediaStream);
setInCall(true);
setIncomingCall(null);
}

const handleDecline = async () => {
if (!brtcClient) return;
await brtcClient.declineStream();
setIncomingCall(null);
}

// Fetch the WebSocket URL from env
const gatewayUrl = process.env.REACT_APP_WSS_URL;

Expand All @@ -70,9 +98,12 @@ function App() {
<Navbar />
{brtcClient && (
<>
<EndpointHandler bandwidthRtcClient={brtcClient} resetClient={resetClient} gatewayUrl={gatewayUrl} />
<EndpointHandler bandwidthRtcClient={brtcClient} resetClient={resetClient} gatewayUrl={gatewayUrl} autoAccept={autoAccept} setAutoAccept={setAutoAccept} />
<hr />
{brtcClientReady}
{incomingCall && (
<IncomingCall stream={incomingCall} onAccept={handleAccept} onDecline={handleDecline} />
)}
{readyMetadata && (
<>
<h2>Bandwidth RTC Agent Sample</h2>
Expand Down
14 changes: 12 additions & 2 deletions src/components/EndpointHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import BandwidthRtc from "bandwidth-rtc";
import {Endpoint} from "../../server/types"
import '../css/EndpointHandler.scss';

function EndpointHandler({bandwidthRtcClient, resetClient, gatewayUrl}: {bandwidthRtcClient: BandwidthRtc, resetClient: () => void, gatewayUrl?: string }) {
function EndpointHandler({bandwidthRtcClient, resetClient, gatewayUrl, autoAccept, setAutoAccept}: {bandwidthRtcClient: BandwidthRtc, resetClient: () => void, gatewayUrl?: string, autoAccept: boolean, setAutoAccept: (autoAccept: boolean) => void }) {
const [endpoint, setEndpoint] = useState<Endpoint | null>(null);
const [banner, setBanner] = useState<{ message: string; isError: boolean } | null>(null);

Expand All @@ -23,7 +23,8 @@ function EndpointHandler({bandwidthRtcClient, resetClient, gatewayUrl}: {bandwid
await bandwidthRtcClient.connect({
endpointToken: endpointData.token
}, {
websocketUrl: gatewayUrl
websocketUrl: gatewayUrl,
autoAccept
}).then(() => {
console.log("WebRTC Client Connected");
}).catch((error) => {
Expand Down Expand Up @@ -88,6 +89,15 @@ function EndpointHandler({bandwidthRtcClient, resetClient, gatewayUrl}: {bandwid
<button onClick={deleteEndpoint} disabled={!endpoint}>Disconnect & Delete Endpoint</button>
<button onClick={deleteAllEndpoints}>Delete All Endpoints</button>
</div>
<label className="auto-accept-toggle" title="Set before creating the endpoint. When off, inbound calls prompt with Accept/Decline.">
<input
type="checkbox"
checked={autoAccept}
disabled={endpoint !== null}
onChange={(e) => setAutoAccept(e.target.checked)}
/>
Auto-accept inbound calls
</label>
{endpoint !== null && (
<p className="endpoint-id">{endpoint.endpointId}</p>
)}
Expand Down
24 changes: 24 additions & 0 deletions src/components/IncomingCall.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import {RtcStream} from "bandwidth-rtc";
import '../css/IncomingCall.scss';

function IncomingCall({stream, onAccept, onDecline}: {stream: RtcStream, onAccept: () => void, onDecline: () => void}) {
const caller = stream.from || "Unknown";

return (
<div className="incoming-call">
<div className="incoming-call-info">
<span className="incoming-call-label">Incoming call</span>
<span className="incoming-call-from">
{caller}{stream.fromType ? ` (${stream.fromType})` : ""}
</span>
</div>
<div className="incoming-call-actions">
<button className="accept" onClick={onAccept}>Accept</button>
<button className="decline" onClick={onDecline}>Decline</button>
</div>
</div>
);
}

export default IncomingCall;
9 changes: 9 additions & 0 deletions src/css/EndpointHandler.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@
word-break: break-all;
padding: 0 8px;
}

.auto-accept-toggle {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
margin-top: 8px;
cursor: pointer;
}
53 changes: 53 additions & 0 deletions src/css/IncomingCall.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// IncomingCall.scss

.incoming-call {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 16px;
max-width: 480px;
margin: 8px auto;
padding: 12px 24px;
background: #e3f2fd;
border: 2px solid #1976d2;
border-radius: 8px;

.incoming-call-info {
display: flex;
flex-direction: column;
align-items: flex-start;
}

.incoming-call-label {
font-weight: 600;
}

.incoming-call-from {
font-size: 0.9rem;
color: #555;
word-break: break-all;
}

.incoming-call-actions {
display: flex;
gap: 8px;
}

button {
border: none;
padding: 8px 20px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
color: white;

&.accept {
background: #4caf50;
}

&.decline {
background: #f44336;
}
}
}