Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/client/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"import/no-unresolved": "off",
"import/extensions": ["off"],
"import/prefer-default-export": "off",
"no-restricted-exports": "warn",

// 접근성 관련 규칙
"jsx-a11y/media-has-caption": "off",
Expand Down
4 changes: 4 additions & 0 deletions apps/client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Sonar
.sonar/
.scannerwork/
4 changes: 3 additions & 1 deletion apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint",
"preview": "vite preview"
"preview": "vite preview",
"sonar": "sonar-scanner"
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.1",
Expand Down Expand Up @@ -49,6 +50,7 @@
"eslint-plugin-react-refresh": "^0.4.14",
"postcss": "^8.4.47",
"prettier": "*",
"sonarqube-scanner": "^4.2.6",
"tailwindcss": "^3.4.14",
"typescript": "*",
"vite": "^5.4.10"
Expand Down
3 changes: 3 additions & 0 deletions apps/client/sonar-project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
sonar.projectKey=CamOn
sonar.sources=.
sonar.host.url=http://localhost:9000
3 changes: 2 additions & 1 deletion apps/client/src/app/providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react';
import { AuthContext } from '@/shared/contexts';
import { ProviderProps } from './types';

export function AuthProvider({ children }: { children: React.ReactNode }) {
export function AuthProvider({ children }: ProviderProps) {
const [isLoggedIn, setIsLoggedIn] = useState(() => !!localStorage.getItem('accessToken'));
const value = useMemo(() => ({ isLoggedIn, setIsLoggedIn }), [isLoggedIn, setIsLoggedIn]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/app/providers/Providers.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ThemeProvider } from '@/app/providers/ThemeProvider';
import { AuthProvider } from '@/app/providers/AuthProvider';
import { ProviderProps } from './types';

export function Providers({ children }: { children: React.ReactNode }) {
export function Providers({ children }: ProviderProps) {
return (
<AuthProvider>
<ThemeProvider>{children}</ThemeProvider>
Expand Down
3 changes: 2 additions & 1 deletion apps/client/src/app/providers/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useMemo, useState } from 'react';
import { ThemeContext } from '@/shared/contexts';
import { ProviderProps } from './types';

type Theme = 'light' | 'dark' | null;

export function ThemeProvider({ children }: { children: React.ReactNode }) {
export function ThemeProvider({ children }: ProviderProps) {
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem('theme') as Theme) ?? null);
const value = useMemo(() => ({ theme, setTheme }), [theme, setTheme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
Expand Down
1 change: 1 addition & 0 deletions apps/client/src/app/providers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ProviderProps = Readonly<{ children: React.ReactNode }>;
40 changes: 29 additions & 11 deletions apps/client/src/app/routes/router.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { createBrowserRouter } from 'react-router-dom';
import { HomePage } from '@pages/Home';
import { LivePage } from '@pages/Live';
import { BroadcastPage } from '@pages/Broadcast';
import { AuthPage } from '@pages/Auth';
import { RecordPage } from '@pages/Record';
import { ProfilePage } from '@/pages/Profile';
import { lazy, Suspense } from 'react';
import { HomePage } from '@/pages/Home';
import { AuthPage } from '@/pages/Auth';
import { Layout } from '@/app/layouts';
import ProtectedRoute from './ProtectedRoute';
import { routerOptions } from './config';
import { LoadingCharacter } from '@/shared/ui';

const LivePage = lazy(() => import('@/pages/Live'));
const BroadcastPage = lazy(() => import('@/pages/Broadcast'));
const ProfilePage = lazy(() => import('@/pages/Profile'));
const RecordPage = lazy(() => import('@/pages/Record'));

export const router = createBrowserRouter(
[
Expand All @@ -21,7 +24,11 @@ export const router = createBrowserRouter(
},
{
path: 'live/:liveId',
element: <LivePage />,
element: (
<Suspense fallback={<LoadingCharacter />}>
<LivePage />
</Suspense>
),
},
{
path: 'auth',
Expand All @@ -33,12 +40,19 @@ export const router = createBrowserRouter(
children: [
{
path: 'profile',
element: <ProfilePage />,
element: (
<Suspense fallback={<LoadingCharacter />}>
<ProfilePage />
</Suspense>
),
},

{
path: 'record/:attendanceId',
element: <RecordPage />,
element: (
<Suspense fallback={<LoadingCharacter />}>
<RecordPage />
</Suspense>
),
},
],
},
Expand All @@ -50,7 +64,11 @@ export const router = createBrowserRouter(
children: [
{
path: '',
element: <BroadcastPage />,
element: (
<Suspense fallback={<LoadingCharacter />}>
<BroadcastPage />
</Suspense>
),
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/features/broadcasting/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { BroadcastPlayer, BroadcastTitle, RecordButton } from './ui';
export { useRoom, useProducer, useMedia, useScreenShare } from './model';
export { useProduce, useMedia, useScreenShare } from './model';

export type { Tracks } from './model';
2 changes: 1 addition & 1 deletion apps/client/src/features/broadcasting/model/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { useMedia } from './useMedia';
export { useRoom, useProducer } from './mediasoup';
export { useProduce } from './mediasoup';
export { useScreenShare } from './useScreenShare';
export type { Tracks } from './trackTypes';
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { useRoom } from './useRoom';
export { useProducer } from './useProducer';
export { useProduce } from './useProduce';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Socket } from 'socket.io-client';
import { Transport } from 'mediasoup-client/lib/types';
import { TransportInfo } from '@/shared/types/mediasoupTypes';
import { ENCODING_OPTIONS } from './encodingOptions';

export const getRoomId = (socket: Socket): Promise<string> =>
new Promise(resolve => {
socket.emit('createRoom', (response: { roomId: string }) => {
resolve(response.roomId);
});
});

export const createProducer = async (
socket: Socket,
roomId: string,
transport: Transport,
transportInfo: TransportInfo,
mediaStream: MediaStream,
) => {
const handleProduce = (parameters: any, callback: any) => {
socket.emit(
'createProducer',
{
roomId,
transportId: transportInfo.transportId,
kind: parameters.kind,
rtpParameters: parameters.rtpParameters,
},
(response: { producerId: string }) => {
callback({ id: response.producerId });
},
);
};

transport.on('produce', handleProduce);

const producers = new Map();

try {
await Promise.all(
mediaStream.getTracks().map(async track => {
const producerConfig: Record<string, unknown> = {
track,
stopTracks: false,
};

if (track.kind === 'video') {
producerConfig.encodings = ENCODING_OPTIONS;
producerConfig.codecOptions = {
videoGoogleStartBitrate: 1000,
};
}

const producer = await transport.produce(producerConfig);
producers.set(track.kind, producer);
}),
);

return producers;
} finally {
transport.off('produce', handleProduce);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Socket } from 'socket.io-client';
import { useEffect, useRef, useState } from 'react';
import { Producer, Transport } from 'mediasoup-client/lib/types';
import { createProducer, getRoomId } from './produceHelpers';
import { connectTransport, createDevice, getRtpCapabilities } from '@/shared/lib';

type UseProduceProps = {
socket: Socket | null;
mediaStream: MediaStream | null;
};

type UseProduceReturn = {
producers: Map<string, Producer>;
error: Error | null;
roomId: string;
transport: Transport | null;
};

export const useProduce = ({ socket, mediaStream }: UseProduceProps): UseProduceReturn => {
const [error, setError] = useState<Error | null>(null);
const [roomId, setRoomId] = useState<string>('');
const producersRef = useRef<Map<string, Producer>>(new Map());
const transportRef = useRef<Transport | null>(null);

useEffect(() => {
if (!socket || !mediaStream) return undefined;
const initializeProducer = async () => {
const newRoomId = await getRoomId(socket);
if (!newRoomId) {
setError(new Error('roomId가 없습니다.'));
return;
}
setRoomId(newRoomId);

const rtpCapabilities = await getRtpCapabilities(socket, newRoomId);
if (!rtpCapabilities) {
setError(new Error('rtpCapabilities가 없습니다.'));
return;
}

const device = await createDevice(rtpCapabilities);
if (!device) {
setError(new Error('device가 없습니다.'));
return;
}

const { transport: newTransport, transportInfo } = await connectTransport(socket, device, newRoomId, true);
if (!newTransport || !transportInfo) {
setError(new Error('transport 연결에 문제가 발생했습니다.'));
return;
}
transportRef.current = newTransport;

const newProducers = await createProducer(socket, newRoomId, newTransport, transportInfo, mediaStream);
producersRef.current = newProducers;
};

initializeProducer();

return () => {
producersRef.current.forEach(producer => producer.close());
if (transportRef.current) {
transportRef.current.close();
}
};
}, [socket, mediaStream]);

return {
roomId,
transport: transportRef.current,
producers: producersRef.current,
error,
};
};
Loading