From 629adde71f0d30954e65ece1f7057c0084c59e04 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:16:58 +0900 Subject: [PATCH 1/8] =?UTF-8?q?refactor:=20StreamData=EB=A5=BC=20client.Re?= =?UTF-8?q?moteStream=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../live/StreamView/List/Pinned.tsx | 9 ++++---- .../live/StreamView/List/SubVideoGrid.tsx | 2 +- .../live/StreamView/List/UnPinned.tsx | 7 +++--- .../live/StreamView/List/VideoGrid.tsx | 11 +++++----- .../src/components/live/StreamView/index.tsx | 12 ---------- apps/web/src/hooks/mediasoup/useMediasoup.ts | 22 +++++++------------ apps/web/src/hooks/useAudioState.ts | 5 +++-- apps/web/src/hooks/usePagination.ts | 19 ++++++++-------- apps/web/src/hooks/usePinnedVideo.ts | 8 ++++--- packages/mediasoup/src/client/index.ts | 9 ++++---- 10 files changed, 45 insertions(+), 59 deletions(-) diff --git a/apps/web/src/components/live/StreamView/List/Pinned.tsx b/apps/web/src/components/live/StreamView/List/Pinned.tsx index 2d2d4af1..81409a22 100644 --- a/apps/web/src/components/live/StreamView/List/Pinned.tsx +++ b/apps/web/src/components/live/StreamView/List/Pinned.tsx @@ -1,4 +1,5 @@ -import { StreamData } from '@/components/live/StreamView'; +import { client } from '@repo/mediasoup'; + import PaginationControls from '@/components/live/StreamView/List/PaginationControls'; import SubVideoGrid from '@/components/live/StreamView/List/SubVideoGrid'; import VideoPlayer from '@/components/live/StreamView/List/VideoPlayer'; @@ -7,11 +8,11 @@ import usePagination from '@/hooks/usePagination'; const ITEMS_PER_SUB_GRID = 4; interface PinnedListProps { - pinnedVideoStreamData: StreamData; + pinnedVideoStreamData: client.RemoteStream; - addPinnedVideo: (stream: StreamData) => void; + addPinnedVideo: (stream: client.RemoteStream) => void; removePinnedVideo: () => void; - getAudioMutedState: (stream: StreamData) => boolean; + getAudioMutedState: (stream: client.RemoteStream) => boolean; } function PinnedGrid({ diff --git a/apps/web/src/components/live/StreamView/List/SubVideoGrid.tsx b/apps/web/src/components/live/StreamView/List/SubVideoGrid.tsx index 67481aa2..90853e3c 100644 --- a/apps/web/src/components/live/StreamView/List/SubVideoGrid.tsx +++ b/apps/web/src/components/live/StreamView/List/SubVideoGrid.tsx @@ -65,7 +65,7 @@ function SubVideoGrid({ nickname={streamData.nickname} stream={streamData.stream ?? null} isMicOn={streamData && getAudioMutedState(streamData)} - mediaType={streamData.consumer?.appData?.mediaTypes} + mediaType={streamData.consumer?.appData?.mediaTypes ?? streamData.mediaType} /> ))} diff --git a/apps/web/src/components/live/StreamView/List/UnPinned.tsx b/apps/web/src/components/live/StreamView/List/UnPinned.tsx index f0166776..12f75fe1 100644 --- a/apps/web/src/components/live/StreamView/List/UnPinned.tsx +++ b/apps/web/src/components/live/StreamView/List/UnPinned.tsx @@ -1,4 +1,5 @@ -import { StreamData } from '@/components/live/StreamView'; +import { client } from '@repo/mediasoup'; + import PaginationControls from '@/components/live/StreamView/List/PaginationControls'; import VideoGrid from '@/components/live/StreamView/List/VideoGrid'; import usePagination from '@/hooks/usePagination'; @@ -6,8 +7,8 @@ import usePagination from '@/hooks/usePagination'; const ITEMS_PER_GRID = 9; interface UnPinnedListProps { - addPinnedVideo: (stream: StreamData) => void; - getAudioMutedState: (stream: StreamData) => boolean; + addPinnedVideo: (stream: client.RemoteStream) => void; + getAudioMutedState: (stream: client.RemoteStream) => boolean; } function UnPinnedGrid({ addPinnedVideo, getAudioMutedState }: UnPinnedListProps) { diff --git a/apps/web/src/components/live/StreamView/List/VideoGrid.tsx b/apps/web/src/components/live/StreamView/List/VideoGrid.tsx index 3f451403..4b37401c 100644 --- a/apps/web/src/components/live/StreamView/List/VideoGrid.tsx +++ b/apps/web/src/components/live/StreamView/List/VideoGrid.tsx @@ -1,6 +1,5 @@ import { cva } from 'class-variance-authority'; - -import { StreamData } from '@/components/live/StreamView'; +import { client } from '@repo/mediasoup'; import VideoPlayer from './VideoPlayer'; @@ -17,9 +16,9 @@ const containerVariants = cva('h-full flex-1 justify-center gap-5', { }); interface VideoGridProps { - videoStreamData: StreamData[]; - onVideoClick: (stream: StreamData) => void; - getAudioMutedState: (stream: StreamData) => boolean; + videoStreamData: client.RemoteStream[]; + onVideoClick: (stream: client.RemoteStream) => void; + getAudioMutedState: (stream: client.RemoteStream) => boolean; } function VideoGrid({ videoStreamData, onVideoClick, getAudioMutedState }: VideoGridProps) { @@ -36,7 +35,7 @@ function VideoGrid({ videoStreamData, onVideoClick, getAudioMutedState }: VideoG nickname={streamData.nickname} stream={streamData.stream ?? null} isMicOn={streamData && getAudioMutedState(streamData)} - mediaType={streamData.consumer?.appData?.mediaTypes} + mediaType={streamData.consumer?.appData?.mediaTypes ?? streamData.mediaType} /> ))} diff --git a/apps/web/src/components/live/StreamView/index.tsx b/apps/web/src/components/live/StreamView/index.tsx index 9cc18655..6f00b685 100644 --- a/apps/web/src/components/live/StreamView/index.tsx +++ b/apps/web/src/components/live/StreamView/index.tsx @@ -1,21 +1,9 @@ -import { types } from 'mediasoup-client'; -import { MediaTypes } from '@repo/mediasoup'; - import AudioStreams from '@/components/live/StreamView/AudioStreams'; import PinnedGrid from '@/components/live/StreamView/List/Pinned'; import UnPinnedGrid from '@/components/live/StreamView/List/UnPinned'; import useAudioState from '@/hooks/useAudioState'; import usePinnedVideo from '@/hooks/usePinnedVideo'; -export interface StreamData { - socketId: string; - nickname: string; - consumer?: types.Consumer<{ mediaTypes: MediaTypes; nickname: string }>; - kind?: types.MediaKind; - stream?: MediaStream | null; - paused?: boolean; -} - const StreamView = () => { const { pinnedVideoStreamData, removePinnedVideo, selectPinnedVideo } = usePinnedVideo(); const { getAudioMutedState } = useAudioState(); diff --git a/apps/web/src/hooks/mediasoup/useMediasoup.ts b/apps/web/src/hooks/mediasoup/useMediasoup.ts index 8909ed12..f3f90a06 100644 --- a/apps/web/src/hooks/mediasoup/useMediasoup.ts +++ b/apps/web/src/hooks/mediasoup/useMediasoup.ts @@ -1,7 +1,6 @@ import { useEffect } from 'react'; import { client, SOCKET_EVENTS } from '@repo/mediasoup'; -import { useDummyStreamAction } from '@/contexts/dummyStream/context'; import { useLocalStreamAction } from '@/contexts/localStream/context'; import { useMediasoupAction, useMediasoupState } from '@/contexts/mediasoup/context'; import { useRemoteStreamAction } from '@/contexts/remoteStream/context'; @@ -22,31 +21,28 @@ const useMediasoup = () => { resumeRemoteStream, resumeAudioConsumers, clearRemoteStream, + addInitialRemoteStream, } = useRemoteStreamAction(); - const { startCameraStream, startMicStream, closeLocalStream } = useLocalStreamAction(); - - const { addDummyStream, removeDummyStream } = useDummyStreamAction(); - + const { startCameraStream, startMicStream, clearLocalStream } = useLocalStreamAction(); const initSocketEvent = () => { const socket = socketRef.current; if (!socket) return; socket.on(SOCKET_EVENTS.newPeer, ({ peerId, nickname }) => { - addDummyStream(peerId, nickname); + addInitialRemoteStream({ socketId: peerId, nickname }); }); socket.on(SOCKET_EVENTS.peerLeft, ({ peerId }) => { - removeDummyStream(peerId); filterRemoteStream((rs) => rs.socketId !== peerId); }); socket.on(SOCKET_EVENTS.consumerClosed, ({ consumerId }) => { - filterRemoteStream((rs) => rs.consumer.id !== consumerId); + filterRemoteStream((rs) => rs.consumer?.id !== consumerId); }); socket.on(SOCKET_EVENTS.producerClosed, ({ producerId }) => { - filterRemoteStream((rs) => rs.consumer.producerId !== producerId); + filterRemoteStream((rs) => rs.consumer?.producerId !== producerId); }); socket.on(SOCKET_EVENTS.producerPaused, ({ producerId }) => { @@ -59,8 +55,6 @@ const useMediasoup = () => { socket.on(SOCKET_EVENTS.newProducer, (data) => { if (socket.id === data.peerId) return; - - removeDummyStream(data.peerId); consume(data); }); }; @@ -103,16 +97,16 @@ const useMediasoup = () => { useEffect(() => { const clearAll = () => { - disconnect(); clearRemoteStream(); - closeLocalStream(); + clearLocalStream(); + disconnect(); }; window.addEventListener('beforeunload', clearAll); return () => { - window.removeEventListener('beforeunload', clearAll); clearAll(); + window.removeEventListener('beforeunload', clearAll); }; }, []); }; diff --git a/apps/web/src/hooks/useAudioState.ts b/apps/web/src/hooks/useAudioState.ts index 00eb7e42..357de286 100644 --- a/apps/web/src/hooks/useAudioState.ts +++ b/apps/web/src/hooks/useAudioState.ts @@ -1,4 +1,5 @@ -import { StreamData } from '@/components/live/StreamView'; +import { client } from '@repo/mediasoup'; + import { useLocalStreamState } from '@/contexts/localStream/context'; import { useRemoteStreamState } from '@/contexts/remoteStream/context'; @@ -6,7 +7,7 @@ function useAudioState() { const { audio, screen } = useLocalStreamState(); const { audioStreams } = useRemoteStreamState(); - const getAudioMutedState = (targetStream: StreamData) => { + const getAudioMutedState = (targetStream: client.RemoteStream) => { if (targetStream.stream?.id === screen.stream?.id) { return false; } diff --git a/apps/web/src/hooks/usePagination.ts b/apps/web/src/hooks/usePagination.ts index 7714a6dd..5eac8caf 100644 --- a/apps/web/src/hooks/usePagination.ts +++ b/apps/web/src/hooks/usePagination.ts @@ -1,8 +1,6 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { client } from '@repo/mediasoup'; -import { StreamData } from '@/components/live/StreamView'; -import { useDummyStreamState } from '@/contexts/dummyStream/context'; import { useLocalStreamState } from '@/contexts/localStream/context'; import { useMediasoupState } from '@/contexts/mediasoup/context'; import { useRemoteStreamAction, useRemoteStreamState } from '@/contexts/remoteStream/context'; @@ -11,14 +9,13 @@ import useAuthStore from '@/stores/useAuthStore'; interface PaginationParams { itemsPerPage: number; - pinnedStream?: StreamData; + pinnedStream?: client.RemoteStream; } const usePagination = ({ itemsPerPage, pinnedStream }: PaginationParams) => { const { socketRef } = useMediasoupState(); const { video, screen } = useLocalStreamState(); - const { dummyStreams } = useDummyStreamState(); const { videoStreams } = useRemoteStreamState(); const nickname = useAuthStore.getState().authInfo?.nickname; @@ -27,11 +24,11 @@ const usePagination = ({ itemsPerPage, pinnedStream }: PaginationParams) => { const [currentPage, setCurrentPage] = useState(0); - const prevPinStreamRef = useRef(); - const prevGridItemsRef = useRef([]); + const prevPinStreamRef = useRef(); + const prevGridItemsRef = useRef([]); const paginatedItems = useMemo(() => { - const totalItems: StreamData[] = []; + const totalItems: client.RemoteStream[] = []; totalItems.push({ socketId: 'local', @@ -39,6 +36,7 @@ const usePagination = ({ itemsPerPage, pinnedStream }: PaginationParams) => { stream: video.stream, paused: video.paused, nickname: nickname ?? '', + mediaType: 'video', }); if (screen.stream) { @@ -48,16 +46,17 @@ const usePagination = ({ itemsPerPage, pinnedStream }: PaginationParams) => { stream: screen.stream, paused: false, nickname: nickname ?? '', + mediaType: 'screen', }); } - totalItems.push(...videoStreams, ...dummyStreams); + totalItems.push(...videoStreams); const startIdx = currentPage * itemsPerPage; const endIdx = startIdx + itemsPerPage; return totalItems.slice(startIdx, endIdx); - }, [videoStreams, currentPage, itemsPerPage, video, screen, nickname, dummyStreams]); + }, [videoStreams, currentPage, itemsPerPage, video, screen, nickname]); const onNextPage = () => { setCurrentPage((prev) => Math.min(totalPages - 1, prev + 1)); @@ -67,7 +66,7 @@ const usePagination = ({ itemsPerPage, pinnedStream }: PaginationParams) => { setCurrentPage((prev) => Math.max(0, prev - 1)); }; - const streamLength = dummyStreams.length + videoStreams.length + 1 + (screen.stream ? 1 : 0); + const streamLength = videoStreams.length + 1 + (screen.stream ? 1 : 0); const totalPages = Math.ceil(streamLength / itemsPerPage); const resumeGridStreams = useDebouncedCallback(() => { diff --git a/apps/web/src/hooks/usePinnedVideo.ts b/apps/web/src/hooks/usePinnedVideo.ts index ea166470..130532c0 100644 --- a/apps/web/src/hooks/usePinnedVideo.ts +++ b/apps/web/src/hooks/usePinnedVideo.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; +import { client } from '@repo/mediasoup'; -import { StreamData } from '@/components/live/StreamView'; import { useLocalStreamState } from '@/contexts/localStream/context'; import { useRemoteStreamState } from '@/contexts/remoteStream/context'; @@ -8,9 +8,11 @@ const usePinnedVideo = () => { const { video, screen, audio } = useLocalStreamState(); const { videoStreams } = useRemoteStreamState(); - const [pinnedVideoStreamData, setPinnedVideoStreamData] = useState(null); + const [pinnedVideoStreamData, setPinnedVideoStreamData] = useState( + null + ); - const selectPinnedVideo = (stream: StreamData) => { + const selectPinnedVideo = (stream: client.RemoteStream) => { setPinnedVideoStreamData(stream); }; diff --git a/packages/mediasoup/src/client/index.ts b/packages/mediasoup/src/client/index.ts index 18c4729e..a6347f15 100644 --- a/packages/mediasoup/src/client/index.ts +++ b/packages/mediasoup/src/client/index.ts @@ -45,11 +45,12 @@ export interface CreateConsumerRes { export interface RemoteStream { socketId: string; - stream: MediaStream; - consumer: types.Consumer<{ mediaTypes: MediaTypes; nickname: string }>; - kind: types.MediaKind; - paused: boolean; + stream?: MediaStream | null; + consumer?: types.Consumer<{ mediaTypes: MediaTypes; nickname: string }>; + kind?: types.MediaKind; + paused?: boolean; nickname: string; + mediaType?: string; } export interface GetProducersRes { From 75a1bfed0b4524cda0cc085f796acb80033e82b7 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:18:03 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20initialStream=EC=9D=B4=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=A0=20=EB=95=8C=20=ED=95=B4=EB=8B=B9=20=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A6=BC=EA=B3=BC=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/hooks/mediasoup/useRemoteStream.ts | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/apps/web/src/hooks/mediasoup/useRemoteStream.ts b/apps/web/src/hooks/mediasoup/useRemoteStream.ts index cc6253f9..c3c207cd 100644 --- a/apps/web/src/hooks/mediasoup/useRemoteStream.ts +++ b/apps/web/src/hooks/mediasoup/useRemoteStream.ts @@ -79,7 +79,7 @@ const useRemoteStream = () => { const consumerIds = consumers .filter((consumer) => consumer.kind === 'audio') - .map((consumer) => consumer.consumer.id); + .map((consumer) => consumer.consumer?.id); const params = { roomId: ticleId, consumerIds }; @@ -103,7 +103,7 @@ const useRemoteStream = () => { const consumerIds = consumers .filter((consumer) => consumer.kind === 'video') - .map((consumer) => consumer.consumer.id); + .map((consumer) => consumer.consumer?.id); const params = { roomId: ticleId, consumerIds }; @@ -127,7 +127,7 @@ const useRemoteStream = () => { const consumerIds = consumers .filter((consumer) => consumer.kind === 'video') - .map((consumer) => consumer.consumer.id); + .map((consumer) => consumer.consumer?.id); const params = { roomId: ticleId, consumerIds }; @@ -139,7 +139,7 @@ const useRemoteStream = () => { const pauseStreamByConsumerId = (consumerId: string) => { return (prevStreams: client.RemoteStream[]) => { const newStreams = prevStreams.map((stream) => { - if (stream.consumer.id === consumerId) { + if (stream.consumer?.id === consumerId) { stream.consumer.pause(); stream.paused = true; } @@ -158,7 +158,7 @@ const useRemoteStream = () => { const resumeStreamByConsumerId = (consumerId: string) => { return (prevStreams: client.RemoteStream[]) => { const newStreams = prevStreams.map((stream) => { - if (stream.consumer.id === consumerId) { + if (stream.consumer?.id === consumerId) { stream.consumer.resume(); stream.paused = false; } @@ -207,15 +207,19 @@ const useRemoteStream = () => { const setRemoteStream = (remoteStream: client.RemoteStream) => { const getNewStreams = (prevStreams: client.RemoteStream[]) => { - const isExist = prevStreams.some( - (stream) => stream.consumer.producerId === remoteStream.consumer.producerId + const newStreams = [...prevStreams]; + + const remoteStreamIdx = prevStreams.findIndex( + (stream) => stream.socketId === remoteStream.socketId && !stream.stream ); - if (isExist) { - return prevStreams; + if (remoteStreamIdx !== -1) { + newStreams[remoteStreamIdx] = remoteStream; + } else { + newStreams.push(remoteStream); } - return [...prevStreams, remoteStream]; + return newStreams; }; if (remoteStream.kind === 'video') { @@ -233,7 +237,7 @@ const useRemoteStream = () => { const deletedStreams = prevStreams.filter((stream) => !cb(stream)); - deletedStreams.forEach((stream) => stream.consumer.close()); + deletedStreams.forEach((stream) => stream.consumer?.close()); return result; }; @@ -251,7 +255,7 @@ const useRemoteStream = () => { const getNewStreams = (prevStreams: client.RemoteStream[]) => { const newStreams = [...prevStreams]; - const stream = newStreams.find((stream) => stream.consumer.producerId === producerId); + const stream = newStreams.find((stream) => stream.consumer?.producerId === producerId); if (!stream) { return prevStreams; @@ -259,10 +263,10 @@ const useRemoteStream = () => { socket.emit(SOCKET_EVENTS.pauseConsumers, { roomId: ticleId, - consumerIds: [stream.consumer.id], + consumerIds: [stream.consumer?.id], }); - stream.consumer.pause(); + stream.consumer?.pause(); stream.paused = true; return newStreams.sort((a, b) => { @@ -276,39 +280,48 @@ const useRemoteStream = () => { setAudioStreams(getNewStreams); }, []); - const resumeRemoteStream = useCallback((producerId: string) => { - const socket = socketRef.current; + const resumeRemoteStream = useCallback( + (producerId: string) => { + const socket = socketRef.current; - if (!socket) { - throw new Error('socket is not initialized'); - } + if (!socket) { + throw new Error('socket is not initialized'); + } - const getNewStreams = (prevStreams: client.RemoteStream[]) => { - const newStreams = [...prevStreams]; - const stream = newStreams.find((stream) => stream.consumer.producerId === producerId); + const getNewStreams = (prevStreams: client.RemoteStream[]) => { + const newStreams = [...prevStreams]; + const stream = newStreams.find((stream) => stream.consumer?.producerId === producerId); - if (!stream) { - return prevStreams; - } + if (!stream) { + return prevStreams; + } - socket.emit(SOCKET_EVENTS.resumeConsumers, { - roomId: ticleId, - consumerIds: [stream.consumer.id], - }); + socket.emit(SOCKET_EVENTS.resumeConsumers, { + roomId: ticleId, + consumerIds: [stream.consumer?.id], + }); - stream.consumer.resume(); - stream.paused = false; + stream.consumer?.resume(); + stream.paused = false; - return newStreams.sort((a, b) => { - if (a.paused === b.paused) return 0; + return newStreams.sort((a, b) => { + if (a.paused === b.paused) return 0; - return a.paused ? 1 : -1; - }); - }; + return a.paused ? 1 : -1; + }); + }; - setVideoStreams(getNewStreams); - setAudioStreams(getNewStreams); - }, []); + setVideoStreams(getNewStreams); + setAudioStreams(getNewStreams); + }, + [socketRef, ticleId] + ); + + const addInitialRemoteStream = ( + initialStream: Pick + ) => { + setVideoStreams((prevStreams) => [...prevStreams, { ...initialStream }]); + }; const clearRemoteStream = () => { setVideoStreams((prevStreams) => { @@ -331,6 +344,7 @@ const useRemoteStream = () => { resumeRemoteStream, resumeAudioConsumers, resumeVideoConsumers, + addInitialRemoteStream, pauseVideoConsumers, clearRemoteStream, }; From aefbdd5f6529080afbd390c3b9c4f5b8e4506024 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:18:23 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20disconnect=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20clearMediasoup=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/contexts/mediasoup/context.ts | 2 +- apps/web/src/contexts/mediasoup/provider.tsx | 12 +++--------- apps/web/src/hooks/mediasoup/useMediasoup.ts | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/web/src/contexts/mediasoup/context.ts b/apps/web/src/contexts/mediasoup/context.ts index da5b39fa..18249afe 100644 --- a/apps/web/src/contexts/mediasoup/context.ts +++ b/apps/web/src/contexts/mediasoup/context.ts @@ -20,7 +20,7 @@ export interface MediasoupState { } interface MediasoupActionContextProps { - disconnect: () => void; + clearMediasoup: () => void; createDevice: (rtpCapabilities: client.RtpCapabilities) => Promise; createSendTransport: (device: client.Device) => Promise; createRecvTransport: (device: client.Device) => Promise; diff --git a/apps/web/src/contexts/mediasoup/provider.tsx b/apps/web/src/contexts/mediasoup/provider.tsx index 146e5531..b3c9b0ca 100644 --- a/apps/web/src/contexts/mediasoup/provider.tsx +++ b/apps/web/src/contexts/mediasoup/provider.tsx @@ -26,23 +26,17 @@ export const MediasoupProvider = ({ children }: MediasoupProviderProps) => { connectExistProducer, } = useProducer({ socketRef, transportsRef }); - const disconnect = () => { + const clearMediasoup = () => { const { recvTransport, sendTransport } = transportsRef.current; - const { audio, video, screen } = producersRef.current; - - socketRef.current?.disconnect(); sendTransport?.close(); recvTransport?.close(); - audio?.close(); - video?.close(); - screen?.close(); + socketRef.current?.disconnect(); socketRef.current = null; deviceRef.current = null; transportsRef.current = { sendTransport: null, recvTransport: null }; - producersRef.current = { audio: null, video: null, screen: null }; }; const state = { @@ -55,7 +49,7 @@ export const MediasoupProvider = ({ children }: MediasoupProviderProps) => { }; const actions = { - disconnect, + clearMediasoup, createDevice, createRecvTransport, createSendTransport, diff --git a/apps/web/src/hooks/mediasoup/useMediasoup.ts b/apps/web/src/hooks/mediasoup/useMediasoup.ts index f3f90a06..e3e7db48 100644 --- a/apps/web/src/hooks/mediasoup/useMediasoup.ts +++ b/apps/web/src/hooks/mediasoup/useMediasoup.ts @@ -11,7 +11,7 @@ const useMediasoup = () => { const { socketRef, isConnected, isError } = useMediasoupState(); const { createRoom } = useRoom(); - const { createRecvTransport, createSendTransport, createDevice, disconnect } = + const { createRecvTransport, createSendTransport, createDevice, clearMediasoup } = useMediasoupAction(); const { consume, @@ -99,7 +99,7 @@ const useMediasoup = () => { const clearAll = () => { clearRemoteStream(); clearLocalStream(); - disconnect(); + clearMediasoup(); }; window.addEventListener('beforeunload', clearAll); From f2e23d1ab45a6450a3a5d4a7aec5b21a5ac9cc9c Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:18:42 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20closeLocalStream=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A5=BC=20clearLocalStream=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/contexts/localStream/context.ts | 3 +- .../web/src/contexts/localStream/provider.tsx | 6 +-- apps/web/src/contexts/remoteStream/context.ts | 3 ++ .../src/contexts/remoteStream/provider.tsx | 2 + .../web/src/hooks/mediasoup/useLocalStream.ts | 45 +++---------------- 5 files changed, 15 insertions(+), 44 deletions(-) diff --git a/apps/web/src/contexts/localStream/context.ts b/apps/web/src/contexts/localStream/context.ts index e1f4c6d2..9b0a242a 100644 --- a/apps/web/src/contexts/localStream/context.ts +++ b/apps/web/src/contexts/localStream/context.ts @@ -28,11 +28,10 @@ interface StreamActionContextProps { startCameraStream: () => void; startMicStream: () => void; startScreenStream: () => void; - closeScreenStream: () => void; pauseStream: (type: MediaTypes) => void; resumeStream: (type: MediaTypes) => void; closeStream: (type: MediaTypes) => void; - closeLocalStream: () => void; + clearLocalStream: () => void; setSelectedVideoDeviceId: (deviceId: string) => void; setSelectedAudioDeviceId: (deviceId: string) => void; diff --git a/apps/web/src/contexts/localStream/provider.tsx b/apps/web/src/contexts/localStream/provider.tsx index 3296a107..92f63f98 100644 --- a/apps/web/src/contexts/localStream/provider.tsx +++ b/apps/web/src/contexts/localStream/provider.tsx @@ -15,11 +15,10 @@ export const LocalStreamProvider = ({ children }: StreamProviderProps) => { startCameraStream, startMicStream, startScreenStream, - closeScreenStream, pauseStream, resumeStream, closeStream, - closeLocalStream, + clearLocalStream, videoDevices, audioDevices, @@ -51,8 +50,7 @@ export const LocalStreamProvider = ({ children }: StreamProviderProps) => { startMicStream, startCameraStream, startScreenStream, - closeScreenStream, - closeLocalStream, + clearLocalStream, setSelectedVideoDeviceId, setSelectedAudioDeviceId, setSelectedAudioOutputDeviceId, diff --git a/apps/web/src/contexts/remoteStream/context.ts b/apps/web/src/contexts/remoteStream/context.ts index 06c27b19..5ed8374d 100644 --- a/apps/web/src/contexts/remoteStream/context.ts +++ b/apps/web/src/contexts/remoteStream/context.ts @@ -19,6 +19,9 @@ interface MediasoupActionContextProps { pauseRemoteStream: (producerId: string) => void; resumeRemoteStream: (producerId: string) => void; clearRemoteStream: () => void; + addInitialRemoteStream: ( + initialStream: Pick + ) => void; } export const RemoteStreamStateContext = createContext(undefined); diff --git a/apps/web/src/contexts/remoteStream/provider.tsx b/apps/web/src/contexts/remoteStream/provider.tsx index acc2c801..81357618 100644 --- a/apps/web/src/contexts/remoteStream/provider.tsx +++ b/apps/web/src/contexts/remoteStream/provider.tsx @@ -23,6 +23,7 @@ export const RemoteStreamProvider = ({ children }: RemoteStreamProviderProps) => resumeAudioConsumers, resumeVideoConsumers, pauseVideoConsumers, + addInitialRemoteStream, } = useRemoteStream(); const state = { audioStreams, videoStreams }; @@ -37,6 +38,7 @@ export const RemoteStreamProvider = ({ children }: RemoteStreamProviderProps) => resumeAudioConsumers, resumeVideoConsumers, pauseVideoConsumers, + addInitialRemoteStream, } as const; return ( diff --git a/apps/web/src/hooks/mediasoup/useLocalStream.ts b/apps/web/src/hooks/mediasoup/useLocalStream.ts index 9f1f4b6e..968e8dd7 100644 --- a/apps/web/src/hooks/mediasoup/useLocalStream.ts +++ b/apps/web/src/hooks/mediasoup/useLocalStream.ts @@ -34,6 +34,7 @@ const useLocalStream = () => { setSelectedAudioOutputDeviceId, getMediaState, + clearStreams, } = useMediaTracks(); const { createProducer, closeProducer, resumeProducer, pauseProducer } = useMediasoupAction(); @@ -80,7 +81,6 @@ const useLocalStream = () => { track.onended = () => { track.stop(); closeStream('screen'); - closeProducer('screen'); }; return createProducer('screen', track); @@ -90,39 +90,15 @@ const useLocalStream = () => { throw e; } }; - - const closeScreenStream = () => { - const [localStream, setLocalStream] = getMediaState('screen'); - const { stream } = localStream; - - setLocalStream({ ...DEFAULT_LOCAL_STREAM }); - - if (!stream) { - return; - } - - closeProducer('screen'); - - stream.getTracks().forEach((track) => { - track.stop(); - }); - }; - const closeStream = (type: MediaTypes) => { - const [localStream, setLocalStream] = getMediaState(type); - - const { stream } = localStream; - - setLocalStream({ ...DEFAULT_LOCAL_STREAM }); - - if (!stream) { - return; - } + const [, setLocalStream] = getMediaState(type); closeProducer(type); - stream.getTracks().forEach((track) => { - track.stop(); + setLocalStream(({ stream }) => { + stream?.getTracks().forEach((track) => track.stop()); + + return { ...DEFAULT_LOCAL_STREAM }; }); }; @@ -162,12 +138,6 @@ const useLocalStream = () => { }); }; - const closeLocalStream = () => { - closeStream('video'); - closeStream('audio'); - closeStream('screen'); - }; - useEffect(() => { if (!selectedVideoDeviceId) return; @@ -208,8 +178,7 @@ const useLocalStream = () => { closeStream, pauseStream, resumeStream, - closeScreenStream, - closeLocalStream, + clearLocalStream: clearStreams, videoDevices, audioDevices, From 3e53e4bb1077aefef9b518b1719e082a6445635a Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:19:03 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20disconnect=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20navigate=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/live/ControlBar/index.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/live/ControlBar/index.tsx b/apps/web/src/components/live/ControlBar/index.tsx index c4b273d8..f1aafafb 100644 --- a/apps/web/src/components/live/ControlBar/index.tsx +++ b/apps/web/src/components/live/ControlBar/index.tsx @@ -1,3 +1,4 @@ +import { useNavigate } from '@tanstack/react-router'; import { SOCKET_EVENTS } from '@repo/mediasoup'; import CameraOffIc from '@/assets/icons/camera-off.svg?react'; @@ -12,7 +13,7 @@ import ToggleButton from '@/components/live/ControlBar/ToggleButton'; import ExitDialog from '@/components/live/ExitDialog'; import SettingDialog from '@/components/live/SettingDialog'; import { useLocalStreamAction, useLocalStreamState } from '@/contexts/localStream/context'; -import { useMediasoupAction, useMediasoupState } from '@/contexts/mediasoup/context'; +import { useMediasoupState } from '@/contexts/mediasoup/context'; import useModal from '@/hooks/useModal'; interface ControlBarProps { @@ -21,6 +22,8 @@ interface ControlBarProps { } const ControlBar = ({ isOwner, onTicleEnd }: ControlBarProps) => { + const navigate = useNavigate({ from: '/live/$ticleId' }); + const { isOpen: isOpenExitModal, onClose: onCloseExitModal, @@ -36,7 +39,6 @@ const ControlBar = ({ isOwner, onTicleEnd }: ControlBarProps) => { const { socketRef } = useMediasoupState(); const { video, screen, audio } = useLocalStreamState(); - const { disconnect } = useMediasoupAction(); const { closeStream, pauseStream, @@ -44,7 +46,6 @@ const ControlBar = ({ isOwner, onTicleEnd }: ControlBarProps) => { startScreenStream, startCameraStream, startMicStream, - closeScreenStream, } = useLocalStreamAction(); const toggleScreenShare = async () => { @@ -52,7 +53,7 @@ const ControlBar = ({ isOwner, onTicleEnd }: ControlBarProps) => { try { if (stream && !paused) { - closeScreenStream(); + closeStream('screen'); } else { startScreenStream(); } @@ -97,7 +98,7 @@ const ControlBar = ({ isOwner, onTicleEnd }: ControlBarProps) => { onTicleEnd(); } - disconnect(); + navigate({ to: '/', replace: true }); }; return ( From 653990e445f887c456f2d089e4dff764acc36c4f Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:19:10 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20clearStreams=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=AF=B8=EB=94=94?= =?UTF-8?q?=EC=96=B4=20=EC=9E=A5=EC=B9=98=20=EC=98=A4=EB=A5=98=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/hooks/useMediaTracks.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/web/src/hooks/useMediaTracks.ts b/apps/web/src/hooks/useMediaTracks.ts index e3448e94..53bb6f09 100644 --- a/apps/web/src/hooks/useMediaTracks.ts +++ b/apps/web/src/hooks/useMediaTracks.ts @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { MediaTypes } from '@repo/mediasoup'; import { LocalStream, MediaDevice } from '@/contexts/localStream/context'; +import { toast } from '@/core/toast'; import { getCameraStream, getMicStream, getScreenStream } from '@/utils/stream'; const DEFAULT_LOCAL_STREAM = { @@ -91,6 +92,21 @@ const useMediaTracks = () => { return track; }; + const clearStreams = () => { + setVideo((prev) => { + prev.stream?.getTracks().forEach((track) => track.stop()); + return { ...DEFAULT_LOCAL_STREAM }; + }); + setAudio((prev) => { + prev.stream?.getTracks().forEach((track) => track.stop()); + return { ...DEFAULT_LOCAL_STREAM }; + }); + setScreen((prev) => { + prev.stream?.getTracks().forEach((track) => track.stop()); + return { ...DEFAULT_LOCAL_STREAM }; + }); + }; + const getMediaState = (type: MediaTypes) => { if (type === 'video') { return [video, setVideo] as const; @@ -123,8 +139,8 @@ const useMediaTracks = () => { if (videoInputs[0]) setSelectedVideoDeviceId(videoInputs[0].value); if (audioInputs[0]) setSelectedAudioDeviceId(audioInputs[0].value); if (audioOutputs[0]) setSelectedAudioOutputDeviceId(audioOutputs[0].value); - } catch (error) { - // console.error('Error fetching media devices:', error); + } catch (_) { + toast('미디어 정보를 가져올 수 없습니다.'); } }; @@ -150,6 +166,7 @@ const useMediaTracks = () => { setSelectedAudioOutputDeviceId, getMediaState, + clearStreams, }; }; From 016f071520ae9d631a95c95f16dec9ddfa43e45f Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:19:26 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20dummyStream=20=EC=BB=A8=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=ED=94=84=EB=A1=9C=EB=B0=94?= =?UTF-8?q?=EC=9D=B4=EB=8D=94=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/contexts/dummyStream/context.ts | 35 ------------------- .../web/src/contexts/dummyStream/provider.tsx | 30 ---------------- 2 files changed, 65 deletions(-) delete mode 100644 apps/web/src/contexts/dummyStream/context.ts delete mode 100644 apps/web/src/contexts/dummyStream/provider.tsx diff --git a/apps/web/src/contexts/dummyStream/context.ts b/apps/web/src/contexts/dummyStream/context.ts deleted file mode 100644 index 2f840616..00000000 --- a/apps/web/src/contexts/dummyStream/context.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { createContext, useContext } from 'react'; - -interface DummyStreamState { - dummyStreams: { socketId: string; nickname: string }[]; -} - -interface MediasoupActionContextProps { - addDummyStream: (socketId: string, nickname: string) => void; - removeDummyStream: (socketId: string) => void; -} - -export const DummyStreamStateContext = createContext(undefined); -export const DummyStreamActionContext = createContext( - undefined -); - -export const useDummyStreamState = (): DummyStreamState => { - const state = useContext(DummyStreamStateContext); - - if (!state) { - throw new Error('useDummyStreamState must be used within a DummyStreamProvider'); - } - - return state; -}; - -export const useDummyStreamAction = (): MediasoupActionContextProps => { - const actions = useContext(DummyStreamActionContext); - - if (!actions) { - throw new Error('useDummyStreamAction must be used within a DummyStreamProvider'); - } - - return actions; -}; diff --git a/apps/web/src/contexts/dummyStream/provider.tsx b/apps/web/src/contexts/dummyStream/provider.tsx deleted file mode 100644 index fa2095e5..00000000 --- a/apps/web/src/contexts/dummyStream/provider.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactNode, useState } from 'react'; - -import { DummyStreamActionContext, DummyStreamStateContext } from '@/contexts/dummyStream/context'; - -interface DummyStreamProviderProps { - children: ReactNode; -} - -export const DummyStreamProvider = ({ children }: DummyStreamProviderProps) => { - const [dummyStreams, setDummyStreams] = useState<{ socketId: string; nickname: string }[]>([]); - - const addDummyStream = (socketId: string, nickname: string) => { - setDummyStreams((prev) => [...prev, { socketId, nickname }]); - }; - - const removeDummyStream = (socketId: string) => { - setDummyStreams((prev) => prev.filter((stream) => stream.socketId !== socketId)); - }; - - const state = { dummyStreams }; - const actions = { addDummyStream, removeDummyStream } as const; - - return ( - - - {children} - - - ); -}; From 1e7dc78a994a73902973a0d71ff0f4fc18f91ca7 Mon Sep 17 00:00:00 2001 From: seoko97 Date: Wed, 4 Dec 2024 00:25:25 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20DummyStreamProvider=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/routes/_authenticated/live/$ticleId.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/web/src/routes/_authenticated/live/$ticleId.tsx b/apps/web/src/routes/_authenticated/live/$ticleId.tsx index 638f699c..4deafe97 100644 --- a/apps/web/src/routes/_authenticated/live/$ticleId.tsx +++ b/apps/web/src/routes/_authenticated/live/$ticleId.tsx @@ -1,7 +1,6 @@ import { createFileRoute } from '@tanstack/react-router'; import MediaContainer from '@/components/live'; -import { DummyStreamProvider } from '@/contexts/dummyStream/provider'; import { LocalStreamProvider } from '@/contexts/localStream/provider'; import { MediasoupProvider } from '@/contexts/mediasoup/provider'; import { RemoteStreamProvider } from '@/contexts/remoteStream/provider'; @@ -15,9 +14,7 @@ function RouteComponent() { - - - +