Skip to content

Commit

Permalink
Initial public release
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasIO committed Oct 24, 2022
0 parents commit c25b0fc
Show file tree
Hide file tree
Showing 37 changed files with 4,611 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 1. Copy this file and rename it to .env.local
# 2. Update the enviroment variables below.

# URL pointing to the LiveKit server.
LIVEKIT_URL=wss://your-host

# API key and secret. If you use LiveKit Cloud this can be generated via the cloud dashboard.
LIVEKIT_API_KEY=<____key_goes_here____>
LIVEKIT_API_SECRET=<____secret_goes_here____>
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel

# typescript
*.tsbuildinfo
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.github/
.next/
node_modules/
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"tabWidth": 2,
"printWidth": 100
}
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# LiveKit Meet

This project is home for a simple video conferencing app built with LiveKit.

## Tech Stack

- This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
- App is built with [livekit-react](https://github.com/livekit/livekit-react/) library

## Demo

Give it a try at https://meet.livekit.io

## Dev Setup

Steps to get a local dev setup up and running:

1. Run `yarn install` to install all dependencies.
2. Copy `.env.example` in the project root and rename it to `.env.local`.
3. Update the missing environment variables in the newly created `.env.local` file.
4. Run `yarn dev` to start the development server and visit [http://localhost:3000](http://localhost:3000) to see the result.
5. Start development 🎉
140 changes: 140 additions & 0 deletions components/ActiveRoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Box, useToast } from '@chakra-ui/react';
import { DisplayContext, DisplayOptions, LiveKitRoom } from '@livekit/react-components';
import { Room, RoomEvent, VideoPresets } from 'livekit-client';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useState } from 'react';
import 'react-aspect-ratio/aspect-ratio.css';
import tinykeys from 'tinykeys';
import { SessionProps, TokenResult } from '../lib/types';
import Controls from './Controls';
import DebugOverlay from './DebugOverlay';

const ActiveRoom = ({
roomName,
identity,
region,
audioTrack,
videoTrack,
turnServer,
forceRelay,
}: SessionProps) => {
const [tokenResult, setTokenResult] = useState<TokenResult>();
const [room, setRoom] = useState<Room>();
const [displayOptions, setDisplayOptions] = useState<DisplayOptions>({
stageLayout: 'grid',
});
const router = useRouter();
const toast = useToast();

useEffect(() => {
// cleanup
return () => {
audioTrack?.stop();
videoTrack?.stop();
};
}, []);

const onLeave = () => {
router.push('/');
};

const onConnected = useCallback(
(room: Room) => {
setRoom(room);
/* @ts-ignore */
window.currentRoom = room;
if (audioTrack) {
room.localParticipant.publishTrack(audioTrack);
}
if (videoTrack) {
room.localParticipant.publishTrack(videoTrack);
}
room.on(RoomEvent.Disconnected, (reason) => {
toast({
title: 'Disconnected',
description: `You've been disconnected from the room`,
duration: 4000,
onCloseComplete: () => {
onLeave();
},
});
});
},
[audioTrack, videoTrack],
);

useEffect(() => {
const params: { [key: string]: string } = {
roomName,
identity,
};
if (region) {
params.region = region;
}
fetch('/api/token?' + new URLSearchParams(params))
.then((res) => res.json())
.then((data: TokenResult) => {
setTokenResult(data);
});
}, []);

useEffect(() => {
if (window) {
let unsubscribe = tinykeys(window, {
'Shift+S': () => {
displayOptions.showStats = displayOptions.showStats ? false : true;
setDisplayOptions(displayOptions);
},
});
return () => {
unsubscribe();
};
}
}, [displayOptions]);

if (!tokenResult) {
return <Box bg="cld.bg1" minH="100vh"></Box>;
}

let rtcConfig: RTCConfiguration | undefined;
if (turnServer) {
rtcConfig = {
iceServers: [turnServer],
iceTransportPolicy: 'relay',
};
} else if (forceRelay) {
rtcConfig = {
iceTransportPolicy: 'relay',
};
}

return (
<DisplayContext.Provider value={displayOptions}>
<Box bg="cld.bg1" height="100vh" padding="0.5rem">
<LiveKitRoom
url={tokenResult.url}
token={tokenResult.token}
onConnected={onConnected}
roomOptions={{
adaptiveStream: true,
dynacast: true,
videoCaptureDefaults: {
resolution: VideoPresets.h720.resolution,
},
publishDefaults: {
screenShareSimulcastLayers: [],
},
}}
connectOptions={{
rtcConfig,
}}
controlRenderer={Controls}
onLeave={onLeave}
/>
{room && <DebugOverlay room={room} />}
</Box>
</DisplayContext.Provider>
);
};

export default ActiveRoom;
19 changes: 19 additions & 0 deletions components/ChatEntry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { HStack, Text } from '@chakra-ui/react';
import { Participant } from 'livekit-client';

export interface ChatData {
sentAt: Date;
message: string;
from?: Participant;
}

const ChatEntry = ({ message, from }: ChatData) => {
return (
<HStack>
{from ? <Text fontWeight={600}>{`${from.name || from.identity}`}:</Text> : null}
<Text>{message}</Text>
</HStack>
);
};

export default ChatEntry;
113 changes: 113 additions & 0 deletions components/ChatOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {
Button,
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerHeader,
DrawerOverlay,
Grid,
GridItem,
HStack,
Textarea,
useDisclosure,
} from '@chakra-ui/react';
import { DataPacket_Kind, Participant, Room, RoomEvent } from 'livekit-client';
import { useEffect, useState } from 'react';
import ChatEntry, { ChatData } from './ChatEntry';

interface ChatProps {
room: Room;
isOpen: boolean;
onClose: () => void;
onUnreadChanged?: (num: number) => void;
}

const encoder = new TextEncoder();
const decoder = new TextDecoder();

const ChatOverlay = ({
room,
isOpen: extIsOpen,
onClose: extOnClose,
onUnreadChanged,
}: ChatProps) => {
const { isOpen, onClose } = useDisclosure({ isOpen: extIsOpen, onClose: extOnClose });
const [input, setInput] = useState<string>();
const [messages, setMessages] = useState<ChatData[]>([]);
const [numUnread, setNumUnread] = useState(0);

useEffect(() => {
const onDataReceived = (payload: Uint8Array, participant?: Participant) => {
const data = decoder.decode(payload);
setMessages((messages) => [
...messages,
{
sentAt: new Date(),
message: data,
from: participant,
},
]);
setNumUnread((numUnread) => numUnread + 1);
};
room.on(RoomEvent.DataReceived, onDataReceived);
return () => {
room.off(RoomEvent.DataReceived, onDataReceived);
};
}, [room]);

useEffect(() => {
if (isOpen) {
setNumUnread(0);
}
}, [isOpen]);

useEffect(() => {
if (onUnreadChanged) {
onUnreadChanged(numUnread);
}
}, [numUnread, onUnreadChanged]);

const sendMessage = () => {
if (!input) {
return;
}
room.localParticipant.publishData(encoder.encode(input), DataPacket_Kind.RELIABLE);
setMessages((messages) => [
...messages,
{
sentAt: new Date(),
message: input,
from: room.localParticipant,
},
]);
setInput('');
};

return (
<Drawer isOpen={isOpen} onClose={onClose} size="sm" placement="left">
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Chat</DrawerHeader>
<DrawerBody>
<Grid height="100%" templateColumns="1fr" templateRows="1fr min-content">
<GridItem>
{messages.map((message, idx) => (
<ChatEntry key={idx} {...message} />
))}
</GridItem>
<GridItem>
<HStack>
<Textarea value={input} onChange={(e) => setInput(e.target.value)} rows={2} />
<Button onClick={sendMessage}>Send</Button>
</HStack>
</GridItem>
</Grid>
</DrawerBody>
</DrawerContent>
</Drawer>
);
};

export default ChatOverlay;
Loading

0 comments on commit c25b0fc

Please sign in to comment.