음 ....
맞다 너무 오랜만에 이 주제를 마무리하는 것 같다. ㅜㅜ
그동안 여러 일이 있었는데 여튼 바빴고, 이 글을 미루고 미뤘다. 반성해야겠다. 😭
그럼 바로 들어가겠다.
내 앱에서 처음 RTC를 사용하는 부분이 바로 Room.tsx 파일이다. 여기서 video와 message를 사용하기 위한 셋팅을 하게되고, 셋팅이 마무리되면 video와 message 작업을 하는 컴포넌트로 넘어가게 된다.
const [isLoading, setIsLoading] = useState(false);
const [start, setStart] = useState(false);
const client = useClient();
const { ready, tracks } = MicrophoneAndCameraTracks();
우선 위의 셋팅을 해야한다. state 값들은 video와 message를 사용할 수 있는지 여부를 저장하는 상태이고,
밑에 부분이 중요하다.
지난 글에서 셋팅한 셋팅 값을 불러오는 값이다.
const client = useClient(); // 생성된 유저 불러오기
const { ready, tracks } = MicrophoneAndCameraTracks();
// 유저의 비디오 & 오디오 값 가져오고 비디오 오디오가 준비 되었는지 ready 값 가져오기
다음 값이 사용 가능해지면 그때 video & message 셋팅을 useEffect에서 시작한다.
useEffect(() => {
const init = async (roomName: string, currentUser: UserDataNId) => {
try {
setIsLoading(true);
// client roomName 방에 입장
const clientUid = await client.join(
config.appId,
roomName,
config.token,
null
);
//비디오 & 오디오 방안 사람들과 공유.
if (tracks) await client.publish([tracks[0], tracks[1]]);
// 메세지 유저정보 만들기
const RTMclient = AgoraRTM.createInstance(config.appId);
await RTMclient.login({ uid: String(clientUid) });
// 현재 user의 displayName 넣기
await RTMclient.addOrUpdateLocalUserAttributes({
name: currentUser.displayName,
});
// roomName 방에 채널을 만들기
const rtmChannel = RTMclient.createChannel(roomName);
// 방에 입장
await rtmChannel.join();
if (clientUid && rtmChannel && tracks) {
const localUser: {
user: IAgoraRTCClient;
tracks: [ICameraVideoTrack, IMicrophoneAudioTrack];
} = {
user: client,
tracks: [tracks[1], tracks[0]],
};
// 만들어진 유저와 rtm channel 그리고 rtmclient는 redux에 저장한다.
dispatch(setLocalUser(localUser));
dispatch(setChannel(rtmChannel));
dispatch(setRtmClient(RTMclient));
setStart(true);
}
setIsLoading(false);
} catch (error) {
console.log(`Room UseEffect has Error!!! : ${error}`);
}
};
if (ready && tracks && client && currentUser) {
console.log("Room starting point", roomId, client);
if (roomId) {
init(roomId, currentUser);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [roomId, client, ready, tracks, currentUser]);
현재 유저가 있고 위 client, ready, tracks 값이 셋팅이 완료되었을 때 roomId가 있으면
init 함수를 실행한다.
init에서는 client의 여러 함수를 사용 방에 입장, 비디오 오디오 공유등의 작업을 한다.
추가적으로 rtm 을 쓰기위해
const RTMclient = AgoraRTM.createInstance(config.appId);
도 만들어준다.
RTC의 client는 이미 useClient로 만들었지만 RTM은 RTC의 client의 uid 값이 있어야 rtc의 client와 동일한 유저의 rtmClient를 만들 수 있기 때문에 위 useEffect에서 만들게 되었다.
위 작업으로 RTMClient랑 rtmchannel까지 만들어졌으면 값들을 redux에 저장하고
맨위에 있는 setStart값을 true로 바꿔서 video와 message에 리스너를 다는 components로 넘어가게 해준다.
return (
<RoomContainer>
<VideoCallContainer>
// 화상채팅 컴포넌트
{ready && tracks && <VideoCall />}
</VideoCallContainer>
{start && (
<>
// 메세지 컴포넌트
<MessageCallContainer
className={messageShow ? "show" : "hide"}
>
<MessageCall />
</MessageCallContainer>
</>
)}
</RoomContainer>
);
videoCall 컴포넌트에서는 실질적으로 rtc client에 즉 현재 나의 video와 audio 에 설정을 더해주는 일을 한다.
또한 새로 channel에 들어오는 유저나 나가는 유저의 video와 audio 값을 듣는 일을 한다.
여기서도 위와 마찬가지로
const client = useClient();
을 하고 useEffect에서 각 상황별로 리스너를 만든다.
useEffect(() => {
const init = async () => {
// remote user가 들어오고 나가고 할 때 event handler
client.on("user-published", async (user, mediaType) => {
try {
await client.subscribe(user, mediaType);
if (mediaType === "video") {
dispatch(addRtcUser(user));
}
if (mediaType === "audio") {
user.audioTrack?.play();
}
} catch (error) {
console.log("rtc subscribe fail", error);
}
});
client.on("user-unpublished", async (user, mediaType) => {
try {
await client.unsubscribe(user, mediaType);
} catch (error) {
console.log("rtc unpublished error", error);
}
});
client.on("user-left", (user) => {
dispatch(removeRtcUser(user));
});
setStart(true);
setIsLoading(false);
};
if (localUser) {
console.log("VideoCall point", localUser);
}
// localUser가 들어오기 전에 이미 존재하는 유저를 구독해야 하기 때문에
init();
}, [localUser, client, dispatch]);
"user-published", "user-unpublished", "user-left" 와 같은 상황에 리스너를 달고
실제 video 화면을 생성하면 구현은 마무리된다.
return (
<Fragment>
{localUser && localUser.tracks && <Controls />}
{start && localUser && localUser.tracks && <Videos />}
</Fragment>
);
Controls 컴포넌트처럼 화면에 다양한 조작을 하는 기능을 넣고 싶으면 유저의 track에 접근해서 구현 할 수 있다.
RTM 또한 위 videoCall처럼 구현하면 된다.
다만
const channel = useSelector(selectRtmChannel);
const rtmClient = useSelector(selectRtmClient);
const messages = useSelector(selectRtmMessages);
저장했던 rtm 관련 값들을 사용한다는 차이가 있다.
rtm에서는 rtmClient보다 channel에서 message를 듣는 과정을 처리하는 일을 많이 한다.
rtm에 리스너를 다는 useEffect는 다음과 같다.
useEffect(() => {
const init = async (channel: RtmChannel, rtmClient: RtmClient) => {
channel.on("MemberJoined", async (MemberId) => {
try {
const { name } = await rtmClient.getUserAttributesByKeys(MemberId, [
"name",
]);
if (name) {
const botMessageData = makeBotMessage("join", name);
if (botMessageData) dispatch(addMessages(botMessageData));
}
} catch (error) {
console.log("Rtm Member join error", error);
}
});
channel.on("MemberLeft", async (MemberId) => {
try {
const botMessageData = makeBotMessage("left");
if (botMessageData) dispatch(addMessages(botMessageData));
} catch (error) {
console.log("Rtm MemberLeft error", error);
}
});
channel.on("ChannelMessage", async (messageData, MemberId) => {
try {
if (messageData.messageType === "TEXT") {
let data = JSON.parse(messageData.text);
const reciveMessageData = {
...data,
from: MESSAGE_TYPE.other,
};
dispatch(addMessages(reciveMessageData));
}
scrollToBottom();
} catch (error) {
console.log(error);
}
});
};
if (channel && rtmClient) {
init(channel, rtmClient);
}
}, [channel, rtmClient, dispatch]);
"ChannelMessage" 만 들어도 message를 주고 받는 것은 가능하다.
"MemberJoined"와 "MemberLeft"를 듣는 이유는 채팅방 안에 특정 유저가 나가거나 들어오게 되면 간단한 봇 메세지를 보여주기 위함이다.
처음으로 자력으로 개발한 앱이기에 여러 부분에서 문제가 많다.
typescript로 변환하는 작업을 진행했지만 type도 제대로 지정을 했는지 의문이다... 🤔 (뭐 any는 없으니 그나마 다행이다 ㅠㅠ)
여튼 agora.io의 SDK는 계속해서 업그레이드되고 있어서 혹시나 agora의 rtc & rtm을 사용해보려 한다면
내 글은 정말 참고 정도만 해주면 좋겠다.
재미있는건 이 앱을 만들고 몇달이 지난후 나몰래 조용히 앱을 이용해본 외국인들이 조금 있다는 것!!!
개선점이나 후기정도 github에 남겨주고 가시지 참... ㅋㅋㅋ
여튼 그래도 시리즈가 마무리되어 다행이다.
더 양질의 글을 쓰려고 노력하도록 하겠다.
위 앱의 github주소와 agora에서 예시로 제공하는 react를 이용한 rtc앱 github주소를 남기며 마무리하겠다.
나의 앱 github주소 : https://github.com/michoball/video-chat-app
agora의 example 주소 : https://github.com/AgoraIO-Community/Agora-RTC-React/blob/master/example/src/App.tsx