Compare commits

...

1 Commits

Author SHA1 Message Date
lukasIO
c7bf395c1d upgrade to next15 2025-01-31 15:24:00 +01:00
6 changed files with 451 additions and 85 deletions

View File

@ -2,14 +2,14 @@ import { videoCodecs } from 'livekit-client';
import { VideoConferenceClientImpl } from './VideoConferenceClientImpl'; import { VideoConferenceClientImpl } from './VideoConferenceClientImpl';
import { isVideoCodec } from '@/lib/types'; import { isVideoCodec } from '@/lib/types';
export default function CustomRoomConnection(props: { export default async function CustomRoomConnection(props: {
searchParams: { searchParams: Promise<{
liveKitUrl?: string; liveKitUrl?: string;
token?: string; token?: string;
codec?: string; codec?: string;
}; }>;
}) { }) {
const { liveKitUrl, token, codec } = props.searchParams; const { liveKitUrl, token, codec } = await props.searchParams;
if (typeof liveKitUrl !== 'string') { if (typeof liveKitUrl !== 'string') {
return <h2>Missing LiveKit URL</h2>; return <h2>Missing LiveKit URL</h2>;
} }

View File

@ -2,25 +2,32 @@ import * as React from 'react';
import { PageClientImpl } from './PageClientImpl'; import { PageClientImpl } from './PageClientImpl';
import { isVideoCodec } from '@/lib/types'; import { isVideoCodec } from '@/lib/types';
export default function Page({ export default async function Page({
params, params,
searchParams, searchParams,
}: { }: {
params: { roomName: string }; params: Promise<{ roomName: string }>;
searchParams: { searchParams: Promise<{
// FIXME: We should not allow values for regions if in playground mode. // FIXME: We should not allow values for regions if in playground mode.
region?: string; region?: string;
hq?: string; hq?: string;
codec?: string; codec?: string;
}; }>;
}) { }) {
const _searchParams = await searchParams;
const _params = await params;
const codec = const codec =
typeof searchParams.codec === 'string' && isVideoCodec(searchParams.codec) typeof _searchParams.codec === 'string' && isVideoCodec(_searchParams.codec)
? searchParams.codec ? _searchParams.codec
: 'vp9'; : 'vp9';
const hq = searchParams.hq === 'true' ? true : false; const hq = _searchParams.hq === 'true' ? true : false;
return ( return (
<PageClientImpl roomName={params.roomName} region={searchParams.region} hq={hq} codec={codec} /> <PageClientImpl
roomName={_params.roomName}
region={_searchParams.region}
hq={hq}
codec={codec}
/>
); );
} }

112
lib/pip.tsx Normal file
View File

@ -0,0 +1,112 @@
import * as React from 'react';
type PiPContextType = {
isSupported: boolean;
pipWindow: Window | null;
requestPipWindow: (width: number, height: number) => Promise<void>;
closePipWindow: () => void;
};
const PiPContext = React.createContext<PiPContextType | undefined>(undefined);
export function usePiPWindow(): PiPContextType {
const context = React.useContext(PiPContext);
if (context === undefined) {
throw new Error('usePiPWindow must be used within a PiPContext');
}
return context;
}
type PiPProviderProps = {
children: React.ReactNode;
};
export function PiPProvider({ children }: PiPProviderProps) {
// Detect if the feature is available.
const isSupported = typeof window !== 'undefined' && 'documentPictureInPicture' in window;
// Expose pipWindow that is currently active
const [pipWindow, setPipWindow] = React.useState<Window | null>(null);
// Close pipWidnow programmatically
const closePipWindow = React.useCallback(() => {
if (pipWindow != null) {
pipWindow.close();
setPipWindow(null);
}
}, [pipWindow]);
// Open new pipWindow
const requestPipWindow = React.useCallback(
async (width: number, height: number) => {
// We don't want to allow multiple requests.
if (pipWindow != null) {
return;
}
// @ts-ignore
const pip = await window.documentPictureInPicture.requestWindow({
width,
height,
});
// Detect when window is closed by user
pip.addEventListener('pagehide', () => {
setPipWindow(null);
});
// It is important to copy all parent widnow styles. Otherwise, there would be no CSS available at all
// https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window
[...document.styleSheets].forEach((styleSheet) => {
try {
const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');
const style = document.createElement('style');
style.textContent = cssRules;
pip.document.head.appendChild(style);
} catch (e) {
const link = document.createElement('link');
if (styleSheet.href == null) {
return;
}
link.rel = 'stylesheet';
link.type = styleSheet.type;
// @ts-ignore
link.media = styleSheet.media;
link.href = styleSheet.href;
pip.document.head.appendChild(link);
}
});
setPipWindow(pip);
},
[pipWindow],
);
const value = React.useMemo(() => {
{
return {
isSupported,
pipWindow,
requestPipWindow,
closePipWindow,
};
}
}, [closePipWindow, isSupported, pipWindow, requestPipWindow]);
return <PiPContext.Provider value={value}>{children}</PiPContext.Provider>;
}
import { createPortal } from 'react-dom';
type PiPWindowProps = {
pipWindow: Window;
children: React.ReactNode;
};
export default function PiPWindow({ pipWindow, children }: PiPWindowProps) {
return createPortal(children, pipWindow.document.body);
}

2
next-env.d.ts vendored
View File

@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@ -15,7 +15,7 @@
"@livekit/krisp-noise-filter": "0.2.14", "@livekit/krisp-noise-filter": "0.2.14",
"livekit-client": "2.8.1", "livekit-client": "2.8.1",
"livekit-server-sdk": "2.9.7", "livekit-server-sdk": "2.9.7",
"next": "14.2.23", "next": "^15",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"tinykeys": "^3.0.0" "tinykeys": "^3.0.0"

389
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff