forked from livekit-examples/meet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c25b0fc
Showing
37 changed files
with
4,611 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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____> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "next/core-web-vitals" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.github/ | ||
.next/ | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"singleQuote": true, | ||
"trailingComma": "all", | ||
"semi": true, | ||
"tabWidth": 2, | ||
"printWidth": 100 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 🎉 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.