Compare commits

...

1 Commits

Author SHA1 Message Date
lebaudantoine 59bb1eccee wip poc pip in picture 2026-01-20 17:17:47 +01:00
3 changed files with 167 additions and 0 deletions
@@ -7,6 +7,7 @@ import { EffectsMenuItem } from './EffectsMenuItem'
import { SupportMenuItem } from './SupportMenuItem'
import { TranscriptMenuItem } from './TranscriptMenuItem'
import { ScreenRecordingMenuItem } from './ScreenRecordingMenuItem'
import { PiPMenuItem } from './PiPMenuItem'
// @todo try refactoring it to use MenuList component
export const OptionsMenuItems = () => {
@@ -20,6 +21,7 @@ export const OptionsMenuItems = () => {
<MenuSection>
<TranscriptMenuItem />
<ScreenRecordingMenuItem />
<PiPMenuItem />
<FullScreenMenuItem />
<EffectsMenuItem />
</MenuSection>
@@ -0,0 +1,126 @@
import { createRoot } from 'react-dom/client'
import React from 'react'
import { MenuItem } from 'react-aria-components'
import { menuRecipe } from '@/primitives/menuRecipe.ts'
import { useRoomContext } from '@livekit/components-react'
import { Button } from '@/primitives'
import { LeaveButton } from '@/features/rooms/livekit/components/controls/LeaveButton'
import { RoomContext } from '@livekit/components-react'
import { RiPictureInPicture2Line } from '@remixicon/react'
import { css } from '@/styled-system/css'
import { HandToggle } from '@/features/rooms/livekit/components/controls/HandToggle'
import { Track } from 'livekit-client'
import { ScreenShareToggle } from '@/features/rooms/livekit/components/controls/ScreenShareToggle.tsx'
import { AudioDevicesControl } from '@/features/rooms/livekit/components/controls/Device/AudioDevicesControl.tsx'
import { VideoDeviceControl } from '@/features/rooms/livekit/components/controls/Device/VideoDeviceControl.tsx'
import { PiPOptionsButton } from '@/features/rooms/livekit/components/controls/Options/PiPOptionsMenuItems.tsx'
// Composant à afficher dans le PiP
const PiPContent = ({ room, onClose }) => {
return (
<RoomContext.Provider value={room}>
<div
className={css({
height: '100vh',
position: 'relative',
backgroundColor: 'primaryDark.50 !important',
})}
>
<h2>Room: {room?.name}</h2>
<Button onPress={onClose} variant="danger">
Close PiP
</Button>
<div
className={css({
position: 'absolute',
margin: '0.25rem',
marginBottom: '0.75rem',
width: 'calc(100% - 0.5rem)',
bottom: '0',
left: '0',
// border: '1px solid white',
display: 'flex',
justifyContent: 'center',
gap: '0.5rem',
})}
>
<AudioDevicesControl
hideMenu={true}
onDeviceError={(error) => console.log(error)}
/>
<VideoDeviceControl
hideMenu={true}
onDeviceError={(error) => console.log(error)}
/>
<ScreenShareToggle onDeviceError={(error) => console.log(error)} />
<HandToggle />
<LeaveButton />
</div>
</div>
</RoomContext.Provider>
)
}
export const PiPMenuItem = () => {
const room = useRoomContext()
const openDocumentPiP = async () => {
try {
if (!('documentPictureInPicture' in window)) {
alert('Document Picture-in-Picture not supported')
return
}
const pipWindow = await window.documentPictureInPicture.requestWindow({
width: 400,
height: 300,
})
// Copier les styles CSS
const allCSS = [...document.styleSheets]
.map((styleSheet) => {
try {
return [...styleSheet.cssRules].map((rule) => rule.cssText).join('')
} catch (e) {
return ''
}
})
.filter(Boolean)
.join('\n')
const styleElement = pipWindow.document.createElement('style')
styleElement.textContent = allCSS
pipWindow.document.head.appendChild(styleElement)
// Créer conteneur React
const container = pipWindow.document.createElement('div')
pipWindow.document.body.appendChild(container)
// Render avec la valeur room passée
const root = createRoot(container)
root.render(
<PiPContent
room={room} // Passer l'objet room
onClose={() => pipWindow.close()}
/>
)
// Nettoyage
pipWindow.addEventListener('pagehide', () => {
root.unmount()
})
} catch (error) {
console.error('Failed to open PiP:', error)
}
}
return (
<MenuItem
onAction={openDocumentPiP}
className={menuRecipe({ icon: true, variant: 'dark' }).item}
>
<RiPictureInPicture2Line size={20} />
Open in PiP
</MenuItem>
)
}
@@ -0,0 +1,39 @@
import { Menu as RACMenu } from 'react-aria-components'
import { useTranslation } from 'react-i18next'
import { RiMore2Line } from '@remixicon/react'
import { Button, Menu } from '@/primitives'
import { OptionsMenuItems } from './OptionsMenuItems'
import { EffectsMenuItem } from '@/features/rooms/livekit/components/controls/Options/EffectsMenuItem'
// @todo try refactoring it to use MenuList component
export const PiPOptionsMenuItems = () => {
return (
<RACMenu
style={{
minWidth: '150px',
width: '300px',
}}
>
<EffectsMenuItem />
</RACMenu>
)
}
export const PiPOptionsButton = () => {
const { t } = useTranslation('rooms')
return (
<Menu variant="dark">
<Button
size="xs"
variant="primaryDark"
aria-label={t('options.buttonLabel')}
tooltip={t('options.buttonLabel')}
>
<RiMore2Line />
</Button>
<PiPOptionsMenuItems />
</Menu>
)
}