Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc9dedb91b | |||
| 3da3641a19 | |||
| 21626b4c4c | |||
| f16cedf5a5 | |||
| 00e4679dc5 |
@@ -53,6 +53,7 @@ def generate_token(room: str, user, username: Optional[str] = None) -> str:
|
|||||||
room=room,
|
room=room,
|
||||||
room_join=True,
|
room_join=True,
|
||||||
room_admin=True,
|
room_admin=True,
|
||||||
|
room_record=True,
|
||||||
can_update_own_metadata=True,
|
can_update_own_metadata=True,
|
||||||
can_publish_sources=[
|
can_publish_sources=[
|
||||||
"camera",
|
"camera",
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { fetchServerApi } from './fetchServerApi'
|
||||||
|
import { buildServerApiUrl } from './buildServerApiUrl'
|
||||||
|
import { useRoomData } from '../hooks/useRoomData'
|
||||||
|
import { useParams } from 'wouter'
|
||||||
|
|
||||||
|
export const useRecordRoom = () => {
|
||||||
|
const data = useRoomData()
|
||||||
|
const { roomId: roomSlug } = useParams()
|
||||||
|
|
||||||
|
const recordRoom = () => {
|
||||||
|
if (!data || !data?.livekit) {
|
||||||
|
throw new Error('Room data is not available')
|
||||||
|
}
|
||||||
|
if (!roomSlug) {
|
||||||
|
throw new Error('Room ID is not available')
|
||||||
|
}
|
||||||
|
return fetchServerApi(
|
||||||
|
buildServerApiUrl(
|
||||||
|
data.livekit.url,
|
||||||
|
'/twirp/livekit.Egress/StartRoomCompositeEgress'
|
||||||
|
),
|
||||||
|
data.livekit.token,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
room_name: data.livekit.room,
|
||||||
|
audio_only: true,
|
||||||
|
file_outputs: [
|
||||||
|
{
|
||||||
|
file_extension: 'ogg',
|
||||||
|
filepath: `{room_name}_{time}_${roomSlug}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopRecordingRoom = (egressId: string) => {
|
||||||
|
if (!data || !data?.livekit) {
|
||||||
|
throw new Error('Room data is not available')
|
||||||
|
}
|
||||||
|
return fetchServerApi(
|
||||||
|
buildServerApiUrl(data.livekit.url, '/twirp/livekit.Egress/StopEgress'),
|
||||||
|
data.livekit.token,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
egressId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { recordRoom, stopRecordingRoom }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { useRoomContext } from '@livekit/components-react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { RoomEvent } from 'livekit-client'
|
||||||
|
|
||||||
|
export const RecordingIndicator = () => {
|
||||||
|
const room = useRoomContext()
|
||||||
|
const [isRecording, setIsRecording] = useState(room.isRecording)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleRecordingStatusChanges = (isRecording: boolean) => {
|
||||||
|
setIsRecording(isRecording)
|
||||||
|
}
|
||||||
|
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanges)
|
||||||
|
return () => {
|
||||||
|
room.off(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanges)
|
||||||
|
}
|
||||||
|
}, [room])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Room is recording: {isRecording ? 'yes' : 'no'}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+2
@@ -8,6 +8,7 @@ import { MenuItem, Menu as RACMenu } from 'react-aria-components'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dispatch, SetStateAction } from 'react'
|
import { Dispatch, SetStateAction } from 'react'
|
||||||
import { DialogState } from '@/features/rooms/livekit/components/controls/Options/OptionsButton'
|
import { DialogState } from '@/features/rooms/livekit/components/controls/Options/OptionsButton'
|
||||||
|
import { RecordingMenuItem } from './RecordingMenuItem.tsx'
|
||||||
|
|
||||||
// @todo try refactoring it to use MenuList component
|
// @todo try refactoring it to use MenuList component
|
||||||
export const OptionsMenuItems = ({
|
export const OptionsMenuItems = ({
|
||||||
@@ -47,6 +48,7 @@ export const OptionsMenuItems = ({
|
|||||||
<RiSettings3Line size={18} />
|
<RiSettings3Line size={18} />
|
||||||
{t('options.items.settings')}
|
{t('options.items.settings')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<RecordingMenuItem />
|
||||||
</RACMenu>
|
</RACMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import { MenuItem } from 'react-aria-components'
|
||||||
|
import { menuItemRecipe } from '@/primitives/menuItemRecipe.ts'
|
||||||
|
import { RiPauseCircleLine, RiRecordCircleLine } from '@remixicon/react'
|
||||||
|
import { useRecordRoom } from '@/features/rooms/livekit/api/recordRoom.ts'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { egressStore } from '@/stores/egress.tsx'
|
||||||
|
import { useSnapshot } from 'valtio'
|
||||||
|
|
||||||
|
export const RecordingMenuItem = () => {
|
||||||
|
const { recordRoom, stopRecordingRoom } = useRecordRoom()
|
||||||
|
|
||||||
|
const egressSnap = useSnapshot(egressStore)
|
||||||
|
const egressId = egressSnap.egressId
|
||||||
|
|
||||||
|
const [isPending, setIsPending] = useState(false)
|
||||||
|
|
||||||
|
const handleAction = async () => {
|
||||||
|
if (egressId) {
|
||||||
|
setIsPending(true)
|
||||||
|
const response = await stopRecordingRoom(egressId)
|
||||||
|
console.log(response)
|
||||||
|
egressStore.egressId = undefined
|
||||||
|
setIsPending(false)
|
||||||
|
} else {
|
||||||
|
setIsPending(true)
|
||||||
|
const response = await recordRoom()
|
||||||
|
egressStore.egressId = response['egress_id'] as string
|
||||||
|
setIsPending(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
isDisabled={isPending}
|
||||||
|
className={menuItemRecipe({ icon: true })}
|
||||||
|
onAction={handleAction}
|
||||||
|
>
|
||||||
|
{egressId ? (
|
||||||
|
<>
|
||||||
|
<RiPauseCircleLine size={18} />
|
||||||
|
Stop recording room
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RiRecordCircleLine size={18} />
|
||||||
|
Record room
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ import { participantsStore } from '@/stores/participants'
|
|||||||
import { FocusLayout } from '../components/FocusLayout'
|
import { FocusLayout } from '../components/FocusLayout'
|
||||||
import { ParticipantTile } from '../components/ParticipantTile'
|
import { ParticipantTile } from '../components/ParticipantTile'
|
||||||
import { MainNotificationToast } from '@/features/notifications/MainNotificationToast'
|
import { MainNotificationToast } from '@/features/notifications/MainNotificationToast'
|
||||||
|
import { RecordingIndicator } from '@/features/rooms/livekit/components/RecordingIndicator.tsx'
|
||||||
|
|
||||||
const LayoutWrapper = styled(
|
const LayoutWrapper = styled(
|
||||||
'div',
|
'div',
|
||||||
@@ -184,6 +185,7 @@ export function VideoConference({
|
|||||||
onWidgetChange={widgetUpdate}
|
onWidgetChange={widgetUpdate}
|
||||||
>
|
>
|
||||||
<div className="lk-video-conference-inner">
|
<div className="lk-video-conference-inner">
|
||||||
|
<RecordingIndicator />
|
||||||
<LayoutWrapper>
|
<LayoutWrapper>
|
||||||
<div
|
<div
|
||||||
style={{ display: 'flex', position: 'relative', width: '100%' }}
|
style={{ display: 'flex', position: 'relative', width: '100%' }}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { proxy } from 'valtio'
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
egressId: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const egressStore = proxy<State>({
|
||||||
|
egressId: undefined,
|
||||||
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
image:
|
image:
|
||||||
repository: lasuite/meet-backend
|
repository: lasuite/meet-backend
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
tag: "main"
|
tag: "hackathon"
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
migrateJobAnnotations:
|
migrateJobAnnotations:
|
||||||
@@ -108,7 +108,7 @@ frontend:
|
|||||||
image:
|
image:
|
||||||
repository: lasuite/meet-frontend
|
repository: lasuite/meet-frontend
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
tag: "main"
|
tag: "hackathon"
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
Reference in New Issue
Block a user