304 lines
8.3 KiB
TypeScript
304 lines
8.3 KiB
TypeScript
import { useMemo, useState } from 'react';
|
|
|
|
import openviduCropped from './assets/openvidu_grey_bg_transp_cropped.png';
|
|
import './App.css';
|
|
import {
|
|
LocalTrackPublication,
|
|
RemoteTrack,
|
|
RemoteTrackPublication,
|
|
Room,
|
|
RoomEvent,
|
|
} from 'livekit-client';
|
|
import axios from 'axios';
|
|
import OvVideo from './OvVideo';
|
|
import OvAudio from './OvAudio';
|
|
|
|
const App = () => {
|
|
// For local development, leave these variables empty
|
|
// For production, configure them with correct URLs depending on your deployment
|
|
let APPLICATION_SERVER_URL = '';
|
|
let LIVEKIT_URL = '';
|
|
|
|
// If APPLICATION_SERVER_URL is not configured, use default value from local development
|
|
if (!APPLICATION_SERVER_URL) {
|
|
if (window.location.hostname === 'localhost') {
|
|
APPLICATION_SERVER_URL = 'http://localhost:6080/';
|
|
} else {
|
|
APPLICATION_SERVER_URL = 'https://' + window.location.hostname + ':6443/';
|
|
}
|
|
}
|
|
|
|
// If LIVEKIT_URL is not configured, use default value from local development
|
|
if (!LIVEKIT_URL) {
|
|
if (window.location.hostname === 'localhost') {
|
|
LIVEKIT_URL = 'ws://localhost:7880/';
|
|
} else {
|
|
LIVEKIT_URL = 'wss://' + window.location.hostname + ':7443/';
|
|
}
|
|
}
|
|
|
|
const [myRoomName, setMyRoomName] = useState('');
|
|
const [myParticipantName, setMyParticipantName] = useState('');
|
|
const [room, setRoom] = useState<Room | undefined>(undefined);
|
|
const [myMainPublication, setMyMainPublication] = useState<
|
|
LocalTrackPublication | RemoteTrackPublication | undefined
|
|
>(undefined);
|
|
const [localPublication, setLocalPublication] = useState<
|
|
LocalTrackPublication | undefined
|
|
>(undefined);
|
|
const [remotePublications, setRemotePublications] = useState<
|
|
RemoteTrackPublication[]
|
|
>([]);
|
|
|
|
const joinRoom = () => {
|
|
|
|
// --- 1) Get a Room object ---
|
|
|
|
const room = new Room();
|
|
setRoom(room);
|
|
|
|
// --- 2) Specify the actions when events take place in the room ---
|
|
|
|
// On every new Track received...
|
|
room.on(
|
|
RoomEvent.TrackSubscribed,
|
|
(_track: RemoteTrack, publication: RemoteTrackPublication) => {
|
|
// Store the new publication in remotePublications array
|
|
setRemotePublications((prevPublications) => [
|
|
...prevPublications,
|
|
publication,
|
|
]);
|
|
}
|
|
);
|
|
|
|
// On every track destroyed...
|
|
room.on(
|
|
RoomEvent.TrackUnsubscribed,
|
|
(_track: RemoteTrack, publication: RemoteTrackPublication) => {
|
|
// Remove the publication from 'remotePublications' array
|
|
deleteRemoteTrackPublication(publication);
|
|
}
|
|
);
|
|
|
|
getToken(myRoomName, myParticipantName).then(async (token: string) => {
|
|
// First param is the LiveKit server URL. Second param is the access token
|
|
try {
|
|
await room.connect(LIVEKIT_URL, token);
|
|
// --- 4) Publish your local tracks ---
|
|
await room.localParticipant.setMicrophoneEnabled(true);
|
|
const videoPublication = await room.localParticipant.setCameraEnabled(
|
|
true
|
|
);
|
|
|
|
// Set the main video in the page to display our webcam and store our localPublication
|
|
setLocalPublication(videoPublication);
|
|
setMyMainPublication(videoPublication);
|
|
} catch (error) {
|
|
console.log(
|
|
'There was an error connecting to the room:',
|
|
error.code,
|
|
error.message
|
|
);
|
|
}
|
|
});
|
|
};
|
|
|
|
const leaveRoom = () => {
|
|
// --- 5) Leave the room by calling 'disconnect' method over the Room object ---
|
|
|
|
if (room) {
|
|
room.disconnect();
|
|
}
|
|
|
|
// Empty all properties...
|
|
setRemotePublications([]);
|
|
setLocalPublication(undefined);
|
|
setRoom(new Room());
|
|
|
|
};
|
|
|
|
const deleteRemoteTrackPublication = useMemo(
|
|
() => (publication: RemoteTrackPublication) => {
|
|
setRemotePublications((prevPublications) =>
|
|
prevPublications.filter((p) => p !== publication)
|
|
);
|
|
},
|
|
[]
|
|
);
|
|
|
|
const handleMainVideoStream = (
|
|
publication: LocalTrackPublication | RemoteTrackPublication
|
|
) => {
|
|
if (publication) {
|
|
setMyMainPublication(publication);
|
|
}
|
|
};
|
|
|
|
const switchCamera = async () => {
|
|
const localDevices: MediaDeviceInfo[] = await Room.getLocalDevices();
|
|
const videoDevices = localDevices.filter(
|
|
(device) => device.kind === 'videoinput'
|
|
);
|
|
const newDevice = videoDevices.find(
|
|
(device) =>
|
|
device.deviceId !==
|
|
localPublication?.track?.mediaStreamTrack.getSettings().deviceId
|
|
);
|
|
if (!newDevice) return;
|
|
room?.switchActiveDevice('videoinput', newDevice?.deviceId);
|
|
};
|
|
|
|
/**
|
|
* --------------------------------------------
|
|
* GETTING A TOKEN FROM YOUR APPLICATION SERVER
|
|
* --------------------------------------------
|
|
* The methods below request the creation of a Token to
|
|
* your application server. This keeps your OpenVidu deployment secure.
|
|
*
|
|
* In this sample code, there is no user control at all. Anybody could
|
|
* access your application server endpoints! In a real production
|
|
* environment, your application server must identify the user to allow
|
|
* access to the endpoints.
|
|
*
|
|
* Visit https://docs.openvidu.io/en/stable/application-server to learn
|
|
* more about the integration of OpenVidu in your application server.
|
|
*/
|
|
const getToken = useMemo(
|
|
() =>
|
|
async (roomName: string, participantName: string): Promise<string> => {
|
|
try {
|
|
const response = await axios.post(
|
|
APPLICATION_SERVER_URL + 'token',
|
|
{ roomName, participantName },
|
|
{
|
|
headers: { 'Content-Type': 'application/json' },
|
|
responseType: 'text',
|
|
}
|
|
);
|
|
return response.data;
|
|
} catch (error) {
|
|
// Handle errors here
|
|
console.error('Error getting token:', error);
|
|
throw error;
|
|
}
|
|
},
|
|
[APPLICATION_SERVER_URL]
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{localPublication === undefined ? (
|
|
<div id="join">
|
|
<div id="img-div">
|
|
<img src={openviduCropped} alt="OpenVidu logo" />
|
|
</div>
|
|
<div id="join-dialog" className="jumbotron vertical-center">
|
|
<h1> Join a video room </h1>
|
|
<form
|
|
className="form-group"
|
|
onSubmit={(e) => {
|
|
joinRoom();
|
|
e.preventDefault();
|
|
}}
|
|
>
|
|
<p>
|
|
<label>Participant: </label>
|
|
<input
|
|
className="form-control"
|
|
type="text"
|
|
id="userName"
|
|
value={myParticipantName}
|
|
onChange={(event) => setMyParticipantName(event.target.value)}
|
|
required
|
|
/>
|
|
</p>
|
|
<p>
|
|
<label> Room: </label>
|
|
<input
|
|
className="form-control"
|
|
type="text"
|
|
id="roomName"
|
|
value={myRoomName}
|
|
onChange={(event) => setMyRoomName(event.target.value)}
|
|
required
|
|
/>
|
|
</p>
|
|
<p className="text-center">
|
|
<input
|
|
className="btn btn-lg btn-success"
|
|
name="commit"
|
|
type="submit"
|
|
value="JOIN"
|
|
/>
|
|
</p>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div id="room">
|
|
<div id="room-header">
|
|
<h1 id="room-title">{myRoomName}</h1>
|
|
<div className="button-container">
|
|
<input
|
|
className="btn btn-large btn-danger"
|
|
type="button"
|
|
id="buttonLeaveRoom"
|
|
onClick={() => leaveRoom()}
|
|
value="Leave room"
|
|
/>
|
|
<input
|
|
className="btn btn-large btn-success"
|
|
type="button"
|
|
id="buttonSwitchCamera"
|
|
onClick={() => switchCamera()}
|
|
value="Switch Camera"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{myMainPublication !== undefined ? (
|
|
<div id="main-video" className="col-md-6">
|
|
{myMainPublication.videoTrack && (
|
|
<OvVideo track={myMainPublication.videoTrack} />
|
|
)}
|
|
</div>
|
|
) : null}
|
|
<div id="video-container" className="col-md-6">
|
|
{localPublication !== undefined ? (
|
|
// sss
|
|
<div className="stream-container col-md-6 col-xs-6">
|
|
{localPublication.videoTrack && (
|
|
<OvVideo
|
|
onClick={() => handleMainVideoStream(localPublication)}
|
|
track={localPublication.videoTrack}
|
|
/>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
{remotePublications.map((publication) => (
|
|
<div
|
|
key={publication.trackSid}
|
|
className={`stream-container col-md-6 col-xs-6 ${
|
|
publication.kind === 'audio' ? 'hidden' : ''
|
|
}`}
|
|
>
|
|
{publication.videoTrack && (
|
|
<OvVideo
|
|
onClick={() => handleMainVideoStream(publication)}
|
|
track={publication.videoTrack}
|
|
/>
|
|
)}
|
|
{publication.audioTrack && (
|
|
<OvAudio track={publication.audioTrack} />
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default App;
|